From fe7c78b7e9308a422ba2451624e6b470b92646fd Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sat, 28 Mar 2026 23:22:57 +0530 Subject: [PATCH 1/7] added initial migration step --- config/kit-config.json | 7 ++ config/mailchimp-config.json | 8 -- netlify/functions/newsletter_subscription.ts | 79 ++++++++++---------- package.json | 2 - 4 files changed, 48 insertions(+), 48 deletions(-) create mode 100644 config/kit-config.json delete mode 100644 config/mailchimp-config.json diff --git a/config/kit-config.json b/config/kit-config.json new file mode 100644 index 000000000000..6cc7260b0dde --- /dev/null +++ b/config/kit-config.json @@ -0,0 +1,7 @@ +{ + "tags": { + "Newsletter": 0, + "Meetings": 0, + "TSC Voting": 0 + } +} diff --git a/config/mailchimp-config.json b/config/mailchimp-config.json deleted file mode 100644 index 5f423956d390..000000000000 --- a/config/mailchimp-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "listId": "6e3e437abe", - "interests" : { - "Newsletter": "a7d6314955", - "Meetings": "3505cd49d1", - "TSC Voting": "f7204f9b90" - } -} \ No newline at end of file diff --git a/netlify/functions/newsletter_subscription.ts b/netlify/functions/newsletter_subscription.ts index d8bcea39ef70..f4087fb6efc8 100644 --- a/netlify/functions/newsletter_subscription.ts +++ b/netlify/functions/newsletter_subscription.ts @@ -1,51 +1,54 @@ -import mailchimp from '@mailchimp/mailchimp_marketing'; import type { Handler, HandlerEvent } from '@netlify/functions'; -import md5 from 'md5'; -import config from '../../config/mailchimp-config.json'; +import config from '../../config/kit-config.json'; + +const KIT_BASE = 'https://api.kit.com/v4'; export const handler: Handler = async (event: HandlerEvent) => { - if (event.httpMethod === 'POST') { - const { listId } = config; - const { email, name, interest } = JSON.parse(event.body || ''); - - const subscriberHash = md5(email.toLowerCase()); - - try { - mailchimp.setConfig({ - apiKey: process.env.MAILCHIMP_API_KEY, - server: 'us12' - }); - - const response = await mailchimp.lists.setListMember(listId, - subscriberHash, - { - email_address: email, - merge_fields: { - FNAME: name - }, - status: 'subscribed', - interests: { - [config.interests[interest]]: true - } - }); + if (event.httpMethod !== 'POST') { + return { + statusCode: 405, + body: JSON.stringify({ message: 'The specified HTTP method is not allowed.' }) + }; + } + const { email, name, interest } = JSON.parse(event.body || ''); + const tagId = config.tags[interest as keyof typeof config.tags]; + + const headers = { + 'X-Kit-Api-Key': process.env.KIT_API_KEY!, + 'Content-Type': 'application/json' + }; + + try { + await fetch(`${KIT_BASE}/subscribers`, { + method: 'POST', + headers, + body: JSON.stringify({ email_address: email, first_name: name, state: 'active' }) + }); + + const tagRes = await fetch(`${KIT_BASE}/tags/${tagId}/subscribers`, { + method: 'POST', + headers, + body: JSON.stringify({ email_address: email }) + }); + + if (!tagRes.ok) { + const errBody = await tagRes.json().catch(() => ({})); return { - statusCode: 200, - body: JSON.stringify(response) - }; - } catch (err) { - return { - statusCode: err.status, - body: JSON.stringify(err) + statusCode: tagRes.status, + body: JSON.stringify(errBody) }; } - } else { + + return { + statusCode: 200, + body: JSON.stringify({ message: 'Subscribed successfully.' }) + }; + } catch (err) { return { statusCode: 500, - body: JSON.stringify({ - message: 'The specified HTTP method is not allowed.' - }) + body: JSON.stringify({ message: (err as Error).message }) }; } }; diff --git a/package.json b/package.json index cf1c8937e3b3..06b4a502378e 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@fec/remark-a11y-emoji": "^4.0.2", "@floating-ui/react-dom-interactions": "^0.13.3", "@heroicons/react": "^1.0.6", - "@mailchimp/mailchimp_marketing": "^3.0.80", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.1.0", @@ -88,7 +87,6 @@ "lodash": "^4.17.21", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "md5": "^2.3.0", "mermaid": "9.3.0", "next": "15.5.14", "next-i18next": "^15.3.0", From 8044654145f35684d6e7dba2db024114170a5696 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 29 Mar 2026 00:44:07 +0530 Subject: [PATCH 2/7] updated tags value --- config/kit-config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/kit-config.json b/config/kit-config.json index 6cc7260b0dde..12e7cd9d9dac 100644 --- a/config/kit-config.json +++ b/config/kit-config.json @@ -1,7 +1,7 @@ { "tags": { - "Newsletter": 0, - "Meetings": 0, - "TSC Voting": 0 + "Newsletter": 18208674, + "Meetings": 18208677, + "TSC Voting": 18208683 } } From 06798a2b44ddb2f1e3e40c89184005f6c4065e05 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 29 Mar 2026 00:55:57 +0530 Subject: [PATCH 3/7] minor improvement --- netlify/functions/newsletter_subscription.ts | 42 ++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/netlify/functions/newsletter_subscription.ts b/netlify/functions/newsletter_subscription.ts index f4087fb6efc8..34f7cbce81c1 100644 --- a/netlify/functions/newsletter_subscription.ts +++ b/netlify/functions/newsletter_subscription.ts @@ -15,18 +15,42 @@ export const handler: Handler = async (event: HandlerEvent) => { const { email, name, interest } = JSON.parse(event.body || ''); const tagId = config.tags[interest as keyof typeof config.tags]; + if (!process.env.KIT_API_KEY) { + return { + statusCode: 503, + body: JSON.stringify({ message: 'Subscription is temporarily unavailable. Please try again later.' }) + }; + } + + if (tagId == null || Number(tagId) === 0) { + return { + statusCode: 503, + body: JSON.stringify({ message: 'Subscription is temporarily unavailable. Please try again later.' }) + }; + } + const headers = { - 'X-Kit-Api-Key': process.env.KIT_API_KEY!, + 'X-Kit-Api-Key': process.env.KIT_API_KEY, 'Content-Type': 'application/json' }; try { - await fetch(`${KIT_BASE}/subscribers`, { + const subRes = await fetch(`${KIT_BASE}/subscribers`, { method: 'POST', headers, body: JSON.stringify({ email_address: email, first_name: name, state: 'active' }) }); + if (!subRes.ok) { + await subRes.text().catch(() => undefined); + return { + statusCode: 502, + body: JSON.stringify({ + message: 'Subscription could not be completed. Please try again later.' + }) + }; + } + const tagRes = await fetch(`${KIT_BASE}/tags/${tagId}/subscribers`, { method: 'POST', headers, @@ -34,10 +58,12 @@ export const handler: Handler = async (event: HandlerEvent) => { }); if (!tagRes.ok) { - const errBody = await tagRes.json().catch(() => ({})); + await tagRes.text().catch(() => undefined); return { - statusCode: tagRes.status, - body: JSON.stringify(errBody) + statusCode: 502, + body: JSON.stringify({ + message: 'Subscription could not be completed. Please try again later.' + }) }; } @@ -45,10 +71,12 @@ export const handler: Handler = async (event: HandlerEvent) => { statusCode: 200, body: JSON.stringify({ message: 'Subscribed successfully.' }) }; - } catch (err) { + } catch { return { statusCode: 500, - body: JSON.stringify({ message: (err as Error).message }) + body: JSON.stringify({ + message: 'An unexpected error occurred. Please try again later.' + }) }; } }; From e42257f1fad8bb4ecd486a0ddc54b5473ec831c4 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 5 Apr 2026 01:38:55 +0530 Subject: [PATCH 4/7] minor change --- .../scripts/mailchimp/htmlContent.js | 2 +- package-lock.json | 162 +++--------------- 2 files changed, 28 insertions(+), 136 deletions(-) diff --git a/.github/workflows/scripts/mailchimp/htmlContent.js b/.github/workflows/scripts/mailchimp/htmlContent.js index d132c72f1aa0..8bcce8e59420 100644 --- a/.github/workflows/scripts/mailchimp/htmlContent.js +++ b/.github/workflows/scripts/mailchimp/htmlContent.js @@ -424,7 +424,7 @@ Topic: Cheers,
-AsyncAPI Initiative +AsyncAPI Initiative diff --git a/package-lock.json b/package-lock.json index b1f1be6fd39a..8ba63d65c6ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@fec/remark-a11y-emoji": "^4.0.2", "@floating-ui/react-dom-interactions": "^0.13.3", "@heroicons/react": "^1.0.6", - "@mailchimp/mailchimp_marketing": "^3.0.80", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.1.0", @@ -50,7 +49,6 @@ "lodash": "^4.18.1", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "md5": "^2.3.0", "mermaid": "9.3.0", "next": "15.5.14", "next-i18next": "^15.3.0", @@ -4724,28 +4722,6 @@ "isomorphic-fetch": "^3.0.0" } }, - "node_modules/@mailchimp/mailchimp_marketing": { - "version": "3.0.80", - "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_marketing/-/mailchimp_marketing-3.0.80.tgz", - "integrity": "sha512-Cgz0xPb+1DUjmrl5whAsmqfAChBko+Wf4/PLQE4RvwfPlcq2agfHr1QFiXEhZ8e+GQwQ3hZQn9iLGXwIXwxUCg==", - "license": "Apache 2.0", - "dependencies": { - "dotenv": "^8.2.0", - "superagent": "3.8.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@mailchimp/mailchimp_marketing/node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=10" - } - }, "node_modules/@mdx-js/loader": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-3.1.1.tgz", @@ -11190,15 +11166,6 @@ "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "license": "MIT" }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -11830,15 +11797,6 @@ "dev": true, "license": "MIT" }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -11930,12 +11888,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "license": "MIT" - }, "node_modules/copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", @@ -12112,15 +12064,6 @@ "uncrypto": "^0.1.3" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/crypto-browserify": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", @@ -16050,16 +15993,6 @@ "node": ">=12.20.0" } }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "license": "MIT", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -20618,17 +20551,6 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "license": "MIT" }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -21186,15 +21108,6 @@ "uuid": "^9.0.0" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micro-memoize": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-4.2.0.tgz", @@ -21987,6 +21900,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", + "optional": true, "bin": { "mime": "cli.js" }, @@ -28903,54 +28817,6 @@ "node": ">= 6" } }, - "node_modules/superagent": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", - "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", - "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", - "license": "MIT", - "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.1.1", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/superagent/node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -29973,6 +29839,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -29989,6 +29856,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30005,6 +29873,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30021,6 +29890,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30037,6 +29907,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30053,6 +29924,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30069,6 +29941,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30085,6 +29958,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30101,6 +29975,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30117,6 +29992,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30133,6 +30009,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30149,6 +30026,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30165,6 +30043,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30181,6 +30060,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30197,6 +30077,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30213,6 +30094,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30229,6 +30111,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30245,6 +30128,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30261,6 +30145,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30277,6 +30162,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30293,6 +30179,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30309,6 +30196,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30325,6 +30213,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30341,6 +30230,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30357,6 +30247,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -30373,6 +30264,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ From d321e517f9f60a27b161f3ee847c173464e9eb92 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 12 Apr 2026 18:59:06 +0530 Subject: [PATCH 5/7] minor improvement --- netlify/functions/newsletter_subscription.ts | 64 ++++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/netlify/functions/newsletter_subscription.ts b/netlify/functions/newsletter_subscription.ts index 34f7cbce81c1..1c1e26839ba9 100644 --- a/netlify/functions/newsletter_subscription.ts +++ b/netlify/functions/newsletter_subscription.ts @@ -3,6 +3,21 @@ import type { Handler, HandlerEvent } from '@netlify/functions'; import config from '../../config/kit-config.json'; const KIT_BASE = 'https://api.kit.com/v4'; +const REQUEST_TIMEOUT_MS = 15_000; + +function isAbortError(err: unknown): boolean { + return err instanceof Error && err.name === 'AbortError'; +} + +async function kitFetch(url: string, init: RequestInit): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + try { + return await fetch(url, { ...init, signal: controller.signal }); + } finally { + clearTimeout(timeoutId); + } +} export const handler: Handler = async (event: HandlerEvent) => { if (event.httpMethod !== 'POST') { @@ -12,7 +27,36 @@ export const handler: Handler = async (event: HandlerEvent) => { }; } - const { email, name, interest } = JSON.parse(event.body || ''); + let body: unknown; + try { + body = JSON.parse(event.body ?? '{}'); + } catch { + return { + statusCode: 400, + body: JSON.stringify({ message: 'Invalid request body.' }) + }; + } + + if (body === null || typeof body !== 'object' || Array.isArray(body)) { + return { + statusCode: 400, + body: JSON.stringify({ message: 'Invalid subscription request.' }) + }; + } + + const { email, name, interest } = body as Partial>; + + if ( + typeof email !== 'string' || + typeof interest !== 'string' || + !Object.prototype.hasOwnProperty.call(config.tags, interest) + ) { + return { + statusCode: 400, + body: JSON.stringify({ message: 'Invalid subscription request.' }) + }; + } + const tagId = config.tags[interest as keyof typeof config.tags]; if (!process.env.KIT_API_KEY) { @@ -29,16 +73,18 @@ export const handler: Handler = async (event: HandlerEvent) => { }; } + const firstName = typeof name === 'string' ? name : ''; + const headers = { 'X-Kit-Api-Key': process.env.KIT_API_KEY, 'Content-Type': 'application/json' }; try { - const subRes = await fetch(`${KIT_BASE}/subscribers`, { + const subRes = await kitFetch(`${KIT_BASE}/subscribers`, { method: 'POST', headers, - body: JSON.stringify({ email_address: email, first_name: name, state: 'active' }) + body: JSON.stringify({ email_address: email, first_name: firstName, state: 'active' }) }); if (!subRes.ok) { @@ -51,7 +97,7 @@ export const handler: Handler = async (event: HandlerEvent) => { }; } - const tagRes = await fetch(`${KIT_BASE}/tags/${tagId}/subscribers`, { + const tagRes = await kitFetch(`${KIT_BASE}/tags/${tagId}/subscribers`, { method: 'POST', headers, body: JSON.stringify({ email_address: email }) @@ -71,7 +117,15 @@ export const handler: Handler = async (event: HandlerEvent) => { statusCode: 200, body: JSON.stringify({ message: 'Subscribed successfully.' }) }; - } catch { + } catch (err) { + if (isAbortError(err)) { + return { + statusCode: 504, + body: JSON.stringify({ + message: 'Subscription service timed out. Please try again later.' + }) + }; + } return { statusCode: 500, body: JSON.stringify({ From 202b9242a2af8fa710e74cb40a68d3e39a022c4d Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 12 Apr 2026 19:03:44 +0530 Subject: [PATCH 6/7] minor fix --- netlify/functions/newsletter_subscription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify/functions/newsletter_subscription.ts b/netlify/functions/newsletter_subscription.ts index 1c1e26839ba9..7651fd74c0c2 100644 --- a/netlify/functions/newsletter_subscription.ts +++ b/netlify/functions/newsletter_subscription.ts @@ -49,7 +49,7 @@ export const handler: Handler = async (event: HandlerEvent) => { if ( typeof email !== 'string' || typeof interest !== 'string' || - !Object.prototype.hasOwnProperty.call(config.tags, interest) + !Object.hasOwn(config.tags, interest) ) { return { statusCode: 400, From 1cd8aeb2087ded2fffc6cf26024f0e2c546449e0 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Fri, 17 Apr 2026 20:22:36 +0530 Subject: [PATCH 7/7] moved tagid to env variable --- config/kit-config.json | 7 ---- netlify/functions/newsletter_subscription.ts | 38 ++++++++++++++------ 2 files changed, 28 insertions(+), 17 deletions(-) delete mode 100644 config/kit-config.json diff --git a/config/kit-config.json b/config/kit-config.json deleted file mode 100644 index 12e7cd9d9dac..000000000000 --- a/config/kit-config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tags": { - "Newsletter": 18208674, - "Meetings": 18208677, - "TSC Voting": 18208683 - } -} diff --git a/netlify/functions/newsletter_subscription.ts b/netlify/functions/newsletter_subscription.ts index 7651fd74c0c2..699b4f3b5e6e 100644 --- a/netlify/functions/newsletter_subscription.ts +++ b/netlify/functions/newsletter_subscription.ts @@ -1,10 +1,31 @@ import type { Handler, HandlerEvent } from '@netlify/functions'; -import config from '../../config/kit-config.json'; - const KIT_BASE = 'https://api.kit.com/v4'; const REQUEST_TIMEOUT_MS = 15_000; +const INTEREST_TO_ENV = { + Newsletter: 'KIT_NEWSLETTER_TAG_ID', + Meetings: 'KIT_MEETINGS_TAG_ID', + 'TSC Voting': 'KIT_TSC_TAG_ID' +} as const; + +type ValidInterest = keyof typeof INTEREST_TO_ENV; + +function isValidInterest(s: string): s is ValidInterest { + return Object.hasOwn(INTEREST_TO_ENV, s); +} + +function parseTagId(raw: string | undefined): number | null { + if (raw == null || raw.trim() === '') { + return null; + } + const n = Number(raw.trim()); + if (!Number.isInteger(n) || n <= 0) { + return null; + } + return n; +} + function isAbortError(err: unknown): boolean { return err instanceof Error && err.name === 'AbortError'; } @@ -46,19 +67,13 @@ export const handler: Handler = async (event: HandlerEvent) => { const { email, name, interest } = body as Partial>; - if ( - typeof email !== 'string' || - typeof interest !== 'string' || - !Object.hasOwn(config.tags, interest) - ) { + if (typeof email !== 'string' || typeof interest !== 'string' || !isValidInterest(interest)) { return { statusCode: 400, body: JSON.stringify({ message: 'Invalid subscription request.' }) }; } - const tagId = config.tags[interest as keyof typeof config.tags]; - if (!process.env.KIT_API_KEY) { return { statusCode: 503, @@ -66,7 +81,10 @@ export const handler: Handler = async (event: HandlerEvent) => { }; } - if (tagId == null || Number(tagId) === 0) { + const envVarName = INTEREST_TO_ENV[interest]; + const tagId = parseTagId(process.env[envVarName]); + + if (tagId == null) { return { statusCode: 503, body: JSON.stringify({ message: 'Subscription is temporarily unavailable. Please try again later.' })