From 9a7a011d94b16852cf3d90d5c746da2dcba10c91 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Thu, 27 Feb 2025 13:13:18 +0100 Subject: [PATCH 1/7] WIP: testing newsletter #2839 --- web/package.json | 2 + .../topicPages/Form/SubscribeForm.tsx | 41 ++- web/pages/api/news-letter-distribution.ts | 15 +- web/pages/api/subscribe-form.ts | 50 +++- web/pages/api/subscription.ts | 266 +++++++++++------- web/pnpm-lock.yaml | 23 +- 6 files changed, 264 insertions(+), 133 deletions(-) diff --git a/web/package.json b/web/package.json index 927f91588..46564bd3c 100644 --- a/web/package.json +++ b/web/package.json @@ -42,7 +42,9 @@ "@sanity/client": "5.4.2", "@sanity/image-url": "^1.0.2", "@sanity/webhook": "^2.0.0", + "@types/axios": "^0.14.4", "algoliasearch": "^4.16.0", + "axios": "^1.7.9", "date-fns": "^2.29.3", "date-fns-tz": "^2.0.0", "easy-soap-request": "^5.4.0", diff --git a/web/pageComponents/topicPages/Form/SubscribeForm.tsx b/web/pageComponents/topicPages/Form/SubscribeForm.tsx index 5bcdc77ce..54e7f0a91 100644 --- a/web/pageComponents/topicPages/Form/SubscribeForm.tsx +++ b/web/pageComponents/topicPages/Form/SubscribeForm.tsx @@ -1,4 +1,3 @@ -import type { SubscribeFormParameters } from '../../../types/index' import { Icon } from '@equinor/eds-core-react' import { useForm, Controller } from 'react-hook-form' import { error_filled } from '@equinor/eds-icons' @@ -15,10 +14,20 @@ type FormValues = { categories: string[] } +export type SubscribeFormParameters = { + firstName: string + email: string + crudeOilAssays?: boolean + generalNews?: boolean + magazineStories?: boolean + stockMarketAnnouncements?: boolean + languageCode: string +} + const SubscribeForm = () => { const router = useRouter() const intl = useIntl() - const [isFriendlyChallengeDone, setIsFriendlyChallengeDone] = useState(false) + const [isFriendlyChallengeDone, setIsFriendlyChallengeDone] = useState(process.env.NODE_ENV === 'development') const [isServerError, setServerError] = useState(false) const [isSuccessfullySubmitted, setSuccessfullySubmitted] = useState(false) @@ -29,9 +38,11 @@ const SubscribeForm = () => { register, setError, formState: { errors, isSubmitting, isSubmitted, isSubmitSuccessful }, - } = useForm({ defaultValues: { firstName: '', email: '', categories: [] } }) + } = useForm({ defaultValues: { firstName: '', email: '', categories: [] } }) const onSubmit = async (data: FormValues, event?: BaseSyntheticEvent) => { + console.log('📩 Form submitted with data:', data) + if (isFriendlyChallengeDone) { const allCategories = data.categories.includes('all') const subscribeFormParamers: SubscribeFormParameters = { @@ -41,23 +52,30 @@ const SubscribeForm = () => { generalNews: allCategories || data.categories.includes('generalNews'), stockMarketAnnouncements: allCategories || data.categories.includes('stockMarketAnnouncements'), magazineStories: allCategories || data.categories.includes('magazineStories'), - languageCode: router.locale == 'en' ? 'en' : 'no', + languageCode: router.locale === 'en' ? 'en' : 'no', } + console.log('📝 Sending API request with:', subscribeFormParamers) + const res = await fetch('/api/subscribe-form', { body: JSON.stringify({ subscribeFormParamers, - frcCaptchaSolution: (event?.target as any)['frc-captcha-solution'].value, + frcCaptchaSolution: event?.target?.['frc-captcha-solution']?.value || 'bypass', }), - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, method: 'POST', }) - setServerError(res.status != 200) - setSuccessfullySubmitted(res.status == 200) + + console.log('🔹 API response status:', res.status) + + if (!res.ok) { + const errorResponse = await res.json() + console.error('❌ API error response:', errorResponse) + setServerError(true) + } else { + setSuccessfullySubmitted(true) + } } else { - //@ts-ignore: TODO: types setError('root.notCompletedCaptcha', { type: 'custom', message: intl.formatMessage({ @@ -68,6 +86,7 @@ const SubscribeForm = () => { } } + return ( <> {!isSuccessfullySubmitted && !isServerError && ( diff --git a/web/pages/api/news-letter-distribution.ts b/web/pages/api/news-letter-distribution.ts index a13436265..63ac072ad 100644 --- a/web/pages/api/news-letter-distribution.ts +++ b/web/pages/api/news-letter-distribution.ts @@ -1,6 +1,5 @@ import { distribute } from './subscription' import { languages } from '../../languages' -import { NewsDistributionParameters } from '../../types/index' import { NextApiRequest, NextApiResponse } from 'next' import { isValidSignature, SIGNATURE_HEADER_NAME } from '@sanity/webhook' import getRawBody from 'raw-body' @@ -17,6 +16,18 @@ export const config = { }, } +export type NewsDistributionParameters = { + newsletterId: number + senderId: number + segmentId?: number + timeStamp: string + title: string + ingress: string + link: string + newsType: string + languageCode: string +} + const logRequest = (req: NextApiRequest, title: string) => { console.log('\n') console.log(title) @@ -47,6 +58,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) link: `${publicRuntimeConfig.domain}/${locale}${data.link}`, newsType: data.newsType, languageCode: locale, + newsletterId: data.newsletterId, + senderId: data.senderId, } console.log('Newsletter link: ', newsDistributionParameters.link) diff --git a/web/pages/api/subscribe-form.ts b/web/pages/api/subscribe-form.ts index 7df75e1d4..fae65f062 100644 --- a/web/pages/api/subscribe-form.ts +++ b/web/pages/api/subscribe-form.ts @@ -2,19 +2,41 @@ import { signUp } from './subscription' import { NextApiRequest, NextApiResponse } from 'next' import { validateFormRequest } from './forms/validateFormRequest' -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const result = await validateFormRequest(req, 'subscribe form') - if (result.status !== 200) { - return res.status(result.status).json({ msg: result.message }) +export default async function handler( + req: { body: { subscribeFormParamers: any; frcCaptchaSolution: any }; method: string }, + res: { + status: (arg0: number) => { + (): any + new (): any + json: { (arg0: { error?: any; msg?: string }): any; new (): any } + } + }, +) { + console.log('📥 API received request with:', req.body) + + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method Not Allowed' }) + } + + const { subscribeFormParamers, frcCaptchaSolution } = req.body + + // 🛑 Check if subscribeFormParamers is undefined + if (!subscribeFormParamers || !subscribeFormParamers.email) { + console.error('❌ Missing or invalid subscribeFormParamers:', subscribeFormParamers) + return res.status(400).json({ msg: 'Invalid request data' }) + } + + console.log('✅ Valid request data:', subscribeFormParamers) + + try { + const success = await signUp(subscribeFormParamers) + if (!success) throw new Error('Subscription failed') + + return res.status(200).json({ msg: 'Subscription successful' }) + } catch (error) { + console.error('❌ Error in signUp:', (error as Error).message) + return res.status(500).json({ msg: 'Subscription failed', error: (error as Error).message }) } - const data = req.body - await signUp(data.subscribeFormParamers) - .then((isSuccessful) => { - if (!isSuccessful) { - res.status(500).json({ msg: 'Subscribe failed' }) - } else res.status(200).json({ msg: 'Successfully subscribed.' }) - }) - .catch((error) => { - res.status(500).json({ msg: `Subscribe failed ${error}` }) - }) } + + diff --git a/web/pages/api/subscription.ts b/web/pages/api/subscription.ts index 3bbbf51f3..9b46febcd 100644 --- a/web/pages/api/subscription.ts +++ b/web/pages/api/subscription.ts @@ -1,120 +1,178 @@ -import soapRequest from 'easy-soap-request' -import * as xml2js from 'xml2js' -import { LoginResult, SubscribeFormParameters, NewsDistributionParameters } from '../../types/index' -//import { appInsights } from '../../common' - -const subscriptionUrl = process.env.BRANDMASTER_EMAIL_SUBSCRIPTION_URL || '' -export const authenticationUrl = process.env.BRANDMASTER_EMAIL_AUTHENTICATION_URL || '' -const clientSecret = process.env.BRANDMASTER_EMAIL_CLIENT_SECRET -const password = process.env.BRANDMASTER_EMAIL_PASSWORD -const apnId = process.env.BRANDMASTER_EMAIL_APN_ID -const otyId = process.env.BRANDMASTER_EMAIL_OTY_ID -const ptlId = process.env.BRANDMASTER_EMAIL_PTL_ID - -const sampleHeaders = { - 'Content-Type': 'text/xml;charset=UTF-8', +import axios from 'axios' + +export type SubscribeFormParameters = { + firstName: string + email: string + crudeOilAssays?: boolean + generalNews?: boolean + magazineStories?: boolean + stockMarketAnnouncements?: boolean + languageCode: string } -const xml = `${clientSecret}SUBSCRIPTIONAPI${password}34${ptlId}${otyId}1${apnId}` -const authenticate = async () => { - const { response } = await soapRequest({ - url: authenticationUrl, - headers: sampleHeaders, - xml: xml, - timeout: 5000, - }) - const { body } = response - let apiSecret = '' - let instId = '' - xml2js.parseString(body, function (err, result) { - if (err != null) console.error('Error while authenticating from Brandmaster : ----------------\n' + err) - if (parsedError(result, 'could not get apiSecret and instId ') != undefined) return - - const soapBody = result['SOAP-ENV:Envelope']['SOAP-ENV:Body']['0'] - const loginResult = soapBody['v1:Authentication___LoginResponse']['0']['v1:Result']['0'] - apiSecret = loginResult['v1:apiSecret']['0'] - instId = loginResult['v1:instId']['0'] - }) - return { apiSecret, instId } + +export type NewsDistributionParameters = { + newsletterId: number + senderId: number + segmentId?: number + timeStamp: string + title: string + ingress: string + link: string + newsType: string + languageCode: string } -const createSignUpRequest = async (loginResult: LoginResult, formParameters: SubscribeFormParameters) => { - const additionalParameters = ` - { - "stock_market": "${formParameters.stockMarketAnnouncements ? 'Y' : 'N'}", - "company_news": "${formParameters.generalNews ? 'Y' : 'N'}", - "crude_oil_assays": "${formParameters.crudeOilAssays ? 'Y' : 'N'}", - "magazine": "${formParameters.magazineStories ? 'Y' : 'N'}", - "type": "Investor", - "lang": "${formParameters.languageCode}" - }` - - const envelope = `${clientSecret}${loginResult.apiSecret}${loginResult.instId}${formParameters.firstName}${formParameters.email}${additionalParameters}` - const { response } = await soapRequest({ - url: subscriptionUrl, - headers: sampleHeaders, - xml: envelope, - timeout: 5000, - }) - xml2js.parseString(response.body, function (err, result) { - if (err != null) { - console.log('Error while creating signup request to Brandmaster : ----------------\n' + err) - response.statusCode = 500 - } - if (parsedError(result, 'could not create sign up request ') != undefined) { - response.statusCode = 500 - return - } - }) - return response.statusCode == 200 +const MAKE_SUBSCRIBER_API_BASE_URL = process.env.MAKE_SUBSCRIBER_API_BASE_URL +const MAKE_NEWSLETTER_API_BASE_URL = process.env.MAKE_NEWSLETTER_API_BASE_URL +const MAKE_API_KEY = process.env.MAKE_API_KEY || '' +const SUBSCRIBER_LIST_ID = process.env.MAKE_SUBSCRIBER_LIST_ID +const MAKE_API_USER = process.env.MAKE_API_USERID || '' + +// Axios instance for Subscribers API +const subscriberApi = axios.create({ + baseURL: MAKE_SUBSCRIBER_API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${Buffer.from(`${MAKE_API_USER}:${MAKE_API_KEY}`).toString('base64')}`, + }, +}) + +// Axios instance for Newsletters API +const newsletterApi = axios.create({ + baseURL: MAKE_NEWSLETTER_API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${Buffer.from(`${MAKE_API_USER}:${MAKE_API_KEY}`).toString('base64')}`, + }, +}) + +/** + * Fetch all available subscriber tags + */ +const fetchTags = async () => { + try { + console.log('📤 Fetching subscriber tags...') + const response = await subscriberApi.get(`/subscriber_tags`) + console.log('✅ Tags fetched:', response.data) + return response.data + } catch (error: any) { + console.error('❌ Error fetching subscriber tags:', error.response?.data || error.message) + return [] + } } -const createDistributeRequest = async (loginResult: LoginResult, parameters: NewsDistributionParameters) => { - const envelope = `${clientSecret}${loginResult.apiSecret}${loginResult.instId}${parameters.timeStamp}<![CDATA[${parameters.title}]]>` - const { response } = await soapRequest({ - url: subscriptionUrl, - headers: sampleHeaders, - xml: envelope, - timeout: 5000, - }) - xml2js.parseString(response.body, function (err, result) { - if (err != null) { - console.error('Error while creating distribute request to Brandmaster : ----------------\n' + err) - response.statusCode = 400 - } - const error = parsedError( - result, - 'could not distribute newsletter ' + parameters.link + ' published at ' + parameters.timeStamp, - ) - if (error != undefined) { - // should trigger mail... - console.log('Newsletter distribution failure', response.body.toString()) - // @TODO Move to Dynatrace - // appInsights.trackEvent({name:"Newsletter distribution failure"},{message:error}) - response.statusCode = 400 +/** + * Ensure tags exist before assigning them to a subscriber + */ +const ensureTagsExist = async (requestedTags: string[]) => { + const existingTags = await fetchTags() + const finalTags: string[] = [] + + for (const tagTitle of requestedTags) { + const existingTag = existingTags.find((tag: any) => tag.title.toLowerCase() === tagTitle.toLowerCase()) + + if (existingTag) { + console.log(`✅ Using existing tag: ${existingTag.title}`) + finalTags.push(existingTag.title) + } else { + console.log(`📤 Creating tag: ${tagTitle}`) + try { + const response = await subscriberApi.post(`/subscriber_tags`, { title: tagTitle }) + console.log(`✅ Tag created: ${response.data.title}`) + finalTags.push(response.data.title) + } catch (error: any) { + console.error(`❌ Error creating tag (${tagTitle}):`, error.response?.data || error.message) + } } - }) + } - return response.statusCode == 200 + return finalTags } +/** + * Subscribe a user using subscriber_list_id and tags + */ export const signUp = async (formParameters: SubscribeFormParameters) => { - const loginResult = await authenticate() - if (loginResult.apiSecret != '' && loginResult.instId != '') return createSignUpRequest(loginResult, formParameters) - else return false + try { + console.log('🔹 signUp() called with:', formParameters) + + const requestedTags: string[] = [] + if (formParameters.stockMarketAnnouncements) requestedTags.push('Stock Market') + if (formParameters.generalNews) requestedTags.push('General News') + if (formParameters.crudeOilAssays) requestedTags.push('Crude Oil Assays') + if (formParameters.magazineStories) requestedTags.push('Magazine') + + console.log('📤 Ensuring subscriber tags exist...') + const finalTags = await ensureTagsExist(requestedTags) + + console.log('🔹 Final Tags:', finalTags) + + const requestBody = { + email: formParameters.email, + firstname: formParameters.firstName || '', + lastname: '', + address: '', + zip: '', + city: '', + phone: '', + company: '', + birthday: new Date().toISOString().split('T')[0], + tags: finalTags, + external_id: `ext-${formParameters.email}`, + gender: '', + } + + console.log('📤 Sending subscription request:', { + url: `/subscribers?subscriber_list_id=${SUBSCRIBER_LIST_ID}`, + headers: subscriberApi.defaults.headers, + body: requestBody, + }) + + const response = await subscriberApi.post(`/subscribers?subscriber_list_id=${SUBSCRIBER_LIST_ID}`, requestBody) + + console.log('✅ Successfully subscribed:', response.data) + return response.status === 200 + } catch (error: any) { + console.error('❌ Error in signUp:', { + message: error.message, + responseData: error.response?.data, + responseStatus: error.response?.status, + requestHeaders: error.config?.headers, + }) + return false + } } +/** + * Distribute a newsletter + */ export const distribute = async (parameters: NewsDistributionParameters) => { - const loginResult = await authenticate() - if (loginResult.apiSecret != '' && loginResult.instId != '') { - return createDistributeRequest(loginResult, parameters) - } else return false -} + try { + console.log('🔹 distribute() called with:', parameters) + + const requestBody = { + segment_id: parameters.segmentId ?? null, + sender_id: parameters.senderId, + scheduled_at: new Date(parameters.timeStamp).toISOString(), + } + + console.log('📤 Sending request to newsletter API:', { + url: `/newsletters/${parameters.newsletterId}/send`, + headers: newsletterApi.defaults.headers, + body: requestBody, + }) + + const response = await newsletterApi.post(`/newsletters/${parameters.newsletterId}/send`, requestBody) -const parsedError = (result: any, prefix: string) => { - const soapBody = result['SOAP-ENV:Envelope']['SOAP-ENV:Body']['0'] - if (soapBody['SOAP-ENV:Fault'] != undefined) { - const error = soapBody['SOAP-ENV:Fault']['0']['faultstring'] - console.error(Date() + ' : Newsletter Failure Error: ' + prefix + '\n' + error) - return error + console.log('✅ Success! API response:', response.status, response.data) + return response.status === 200 + } catch (error: any) { + console.error('❌ Error in distribute:', { + message: error.message, + responseData: error.response?.data, + responseStatus: error.response?.status, + requestHeaders: error.config?.headers, + }) + return false } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e299bae72..965963caa 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -67,9 +67,15 @@ dependencies: '@sanity/webhook': specifier: ^2.0.0 version: 2.0.0 + '@types/axios': + specifier: ^0.14.4 + version: 0.14.4 algoliasearch: specifier: ^4.16.0 version: 4.16.0 + axios: + specifier: ^1.7.9 + version: 1.7.9 date-fns: specifier: ^2.29.3 version: 2.29.3 @@ -7042,6 +7048,15 @@ packages: resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} dev: true + /@types/axios@0.14.4: + resolution: {integrity: sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g==} + deprecated: This is a stub types definition. axios provides its own type definitions, so you do not need this installed. + dependencies: + axios: 1.7.9 + transitivePeerDependencies: + - debug + dev: false + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -7898,8 +7913,8 @@ packages: - debug dev: true - /axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + /axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) form-data: 4.0.0 @@ -9091,7 +9106,7 @@ packages: /easy-soap-request@5.4.0: resolution: {integrity: sha512-9LGj3v1ZB1GAYCI127zXxAhubNGP/+j8v44t8DQ2kuFC8kTjG9n3B9WRm4YaoIS1jZRX3YcX5D8Cj0cgdToDLA==} dependencies: - axios: 1.7.7 + axios: 1.7.9 transitivePeerDependencies: - debug dev: false @@ -11274,10 +11289,12 @@ packages: /lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. dev: false /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. dev: false /lodash.isplainobject@4.0.6: From 958fcd7c22ad22ac38c0258991805de4490c626e Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Fri, 28 Feb 2025 15:43:02 +0100 Subject: [PATCH 2/7] WIP: Adding new date --- web/pages/api/subscription.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/pages/api/subscription.ts b/web/pages/api/subscription.ts index 9b46febcd..b28b8c997 100644 --- a/web/pages/api/subscription.ts +++ b/web/pages/api/subscription.ts @@ -151,9 +151,9 @@ export const distribute = async (parameters: NewsDistributionParameters) => { console.log('🔹 distribute() called with:', parameters) const requestBody = { - segment_id: parameters.segmentId ?? null, - sender_id: parameters.senderId, - scheduled_at: new Date(parameters.timeStamp).toISOString(), + segment_id: SUBSCRIBER_LIST_ID, + sender_id: MAKE_API_USER, + scheduled_at: new Date(), } console.log('📤 Sending request to newsletter API:', { From b4d51e15329c370df2941f8e582f6863d6af02d7 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Fri, 28 Feb 2025 13:49:34 +0100 Subject: [PATCH 3/7] WIP: Log request --- web/pages/api/news-letter-distribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/pages/api/news-letter-distribution.ts b/web/pages/api/news-letter-distribution.ts index 63ac072ad..a163a990b 100644 --- a/web/pages/api/news-letter-distribution.ts +++ b/web/pages/api/news-letter-distribution.ts @@ -38,6 +38,7 @@ const logRequest = (req: NextApiRequest, title: string) => { } export default async function handler(req: NextApiRequest, res: NextApiResponse) { + console.log('req', req) console.log('Sending newsletter... ') console.log('Datetime: ' + new Date()) const signature = req.headers[SIGNATURE_HEADER_NAME] as string From 09a8ebb70050ffa578c74a4f558b649c0d0bf7d8 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Fri, 28 Feb 2025 15:44:31 +0100 Subject: [PATCH 4/7] WIP: Adding id and hashing --- web/pages/api/news-letter-distribution.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/pages/api/news-letter-distribution.ts b/web/pages/api/news-letter-distribution.ts index a163a990b..30cbb1c9f 100644 --- a/web/pages/api/news-letter-distribution.ts +++ b/web/pages/api/news-letter-distribution.ts @@ -37,6 +37,16 @@ const logRequest = (req: NextApiRequest, title: string) => { console.log('\n') } +function hashStringToInt(str: string): number { + let hash = 2166136261; + for (let i = 0; i < str.length; i++) { + hash ^= str.charCodeAt(i); + hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); + } + return hash >>> 0; +} + + export default async function handler(req: NextApiRequest, res: NextApiResponse) { console.log('req', req) console.log('Sending newsletter... ') @@ -59,7 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) link: `${publicRuntimeConfig.domain}/${locale}${data.link}`, newsType: data.newsType, languageCode: locale, - newsletterId: data.newsletterId, + newsletterId: hashStringToInt(locale + data.link), senderId: data.senderId, } From 06a45e2f17352e6a95c5f82f7e6c7e8caa583046 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Tue, 4 Mar 2025 15:33:02 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=A8=20Make=20newsletter=20#2839?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/pages/api/news-letter-distribution.ts | 19 +--- web/pages/api/rss/groq.global.ts | 2 + web/pages/api/rss/index.global.ts | 1 + web/pages/api/subscription.ts | 119 +++++----------------- 4 files changed, 28 insertions(+), 113 deletions(-) diff --git a/web/pages/api/news-letter-distribution.ts b/web/pages/api/news-letter-distribution.ts index 30cbb1c9f..3aa7980e0 100644 --- a/web/pages/api/news-letter-distribution.ts +++ b/web/pages/api/news-letter-distribution.ts @@ -17,8 +17,6 @@ export const config = { } export type NewsDistributionParameters = { - newsletterId: number - senderId: number segmentId?: number timeStamp: string title: string @@ -37,18 +35,7 @@ const logRequest = (req: NextApiRequest, title: string) => { console.log('\n') } -function hashStringToInt(str: string): number { - let hash = 2166136261; - for (let i = 0; i < str.length; i++) { - hash ^= str.charCodeAt(i); - hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); - } - return hash >>> 0; -} - - export default async function handler(req: NextApiRequest, res: NextApiResponse) { - console.log('req', req) console.log('Sending newsletter... ') console.log('Datetime: ' + new Date()) const signature = req.headers[SIGNATURE_HEADER_NAME] as string @@ -58,7 +45,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) logRequest(req, 'Unauthorized request: Newsletter Distribution Endpoint') return res.status(401).json({ success: false, msg: 'Unauthorized!' }) } - + const { publicRuntimeConfig } = getConfig() const data = JSON.parse(body) const locale = languages.find((lang) => lang.name == data.languageCode)?.locale || 'en' @@ -69,8 +56,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) link: `${publicRuntimeConfig.domain}/${locale}${data.link}`, newsType: data.newsType, languageCode: locale, - newsletterId: hashStringToInt(locale + data.link), - senderId: data.senderId, } console.log('Newsletter link: ', newsDistributionParameters.link) @@ -107,7 +92,7 @@ async function distributeWithRetry( const date = getDateWithMs() try { - const isSuccessful = await distribute(newsDistributionParameters) + const isSuccessful = await distribute() if (!isSuccessful) throw new Error('Distribution was unsuccessful.') res = { success: true, diff --git a/web/pages/api/rss/groq.global.ts b/web/pages/api/rss/groq.global.ts index 7abad096c..bd168a61a 100644 --- a/web/pages/api/rss/groq.global.ts +++ b/web/pages/api/rss/groq.global.ts @@ -12,6 +12,7 @@ export type LatestNewsType = { publishDateTime: string hero: ImageWithCaptionData ingress: PortableTextBlock + subscriptionType: string } export const latestNews = /* groq */ ` @@ -20,6 +21,7 @@ export const latestNews = /* groq */ ` "slug": slug.current, title, "hero": heroImage, + subscriptionType, "publishDateTime": ${publishDateTimeQuery}, ${ingressForNewsQuery}, } diff --git a/web/pages/api/rss/index.global.ts b/web/pages/api/rss/index.global.ts index 8fa5e3aee..a92c1b2b4 100644 --- a/web/pages/api/rss/index.global.ts +++ b/web/pages/api/rss/index.global.ts @@ -55,6 +55,7 @@ const generateRssFeed = async (lang: 'no' | 'en') => { https://equinor.com${lang === 'no' ? '/no' : ''}${article.slug} ${new Date(article.publishDateTime).toUTCString()}
${descriptionHtml}]]>
+ ${article.subscriptionType ? `${article.subscriptionType}` : ''} ` }) diff --git a/web/pages/api/subscription.ts b/web/pages/api/subscription.ts index b28b8c997..0ec3027f9 100644 --- a/web/pages/api/subscription.ts +++ b/web/pages/api/subscription.ts @@ -10,6 +10,13 @@ export type SubscribeFormParameters = { languageCode: string } +const MAKE_SUBSCRIBER_API_BASE_URL = process.env.MAKE_SUBSCRIBER_API_BASE_URL +const MAKE_NEWSLETTER_API_BASE_URL = process.env.MAKE_NEWSLETTER_API_BASE_URL +const MAKE_API_KEY = process.env.MAKE_API_KEY || '' +const SUBSCRIBER_LIST_ID = process.env.MAKE_SUBSCRIBER_LIST_ID +const MAKE_API_USER = process.env.MAKE_API_USERID || '' +const MAKE_NEWSLETTER_ID = process.env.MAKE_NEWSLETTER_ID + export type NewsDistributionParameters = { newsletterId: number senderId: number @@ -22,13 +29,6 @@ export type NewsDistributionParameters = { languageCode: string } -const MAKE_SUBSCRIBER_API_BASE_URL = process.env.MAKE_SUBSCRIBER_API_BASE_URL -const MAKE_NEWSLETTER_API_BASE_URL = process.env.MAKE_NEWSLETTER_API_BASE_URL -const MAKE_API_KEY = process.env.MAKE_API_KEY || '' -const SUBSCRIBER_LIST_ID = process.env.MAKE_SUBSCRIBER_LIST_ID -const MAKE_API_USER = process.env.MAKE_API_USERID || '' - -// Axios instance for Subscribers API const subscriberApi = axios.create({ baseURL: MAKE_SUBSCRIBER_API_BASE_URL, headers: { @@ -37,89 +37,20 @@ const subscriberApi = axios.create({ }, }) -// Axios instance for Newsletters API -const newsletterApi = axios.create({ - baseURL: MAKE_NEWSLETTER_API_BASE_URL, - headers: { - 'Content-Type': 'application/json', - Authorization: `Basic ${Buffer.from(`${MAKE_API_USER}:${MAKE_API_KEY}`).toString('base64')}`, - }, -}) - -/** - * Fetch all available subscriber tags - */ -const fetchTags = async () => { - try { - console.log('📤 Fetching subscriber tags...') - const response = await subscriberApi.get(`/subscriber_tags`) - console.log('✅ Tags fetched:', response.data) - return response.data - } catch (error: any) { - console.error('❌ Error fetching subscriber tags:', error.response?.data || error.message) - return [] - } -} - -/** - * Ensure tags exist before assigning them to a subscriber - */ -const ensureTagsExist = async (requestedTags: string[]) => { - const existingTags = await fetchTags() - const finalTags: string[] = [] - - for (const tagTitle of requestedTags) { - const existingTag = existingTags.find((tag: any) => tag.title.toLowerCase() === tagTitle.toLowerCase()) - - if (existingTag) { - console.log(`✅ Using existing tag: ${existingTag.title}`) - finalTags.push(existingTag.title) - } else { - console.log(`📤 Creating tag: ${tagTitle}`) - try { - const response = await subscriberApi.post(`/subscriber_tags`, { title: tagTitle }) - console.log(`✅ Tag created: ${response.data.title}`) - finalTags.push(response.data.title) - } catch (error: any) { - console.error(`❌ Error creating tag (${tagTitle}):`, error.response?.data || error.message) - } - } - } - - return finalTags -} - /** * Subscribe a user using subscriber_list_id and tags */ export const signUp = async (formParameters: SubscribeFormParameters) => { try { - console.log('🔹 signUp() called with:', formParameters) - const requestedTags: string[] = [] - if (formParameters.stockMarketAnnouncements) requestedTags.push('Stock Market') - if (formParameters.generalNews) requestedTags.push('General News') - if (formParameters.crudeOilAssays) requestedTags.push('Crude Oil Assays') + if (formParameters.stockMarketAnnouncements) requestedTags.push('Stock') + if (formParameters.generalNews) requestedTags.push('Company') + if (formParameters.crudeOilAssays) requestedTags.push('Crude') if (formParameters.magazineStories) requestedTags.push('Magazine') - console.log('📤 Ensuring subscriber tags exist...') - const finalTags = await ensureTagsExist(requestedTags) - - console.log('🔹 Final Tags:', finalTags) - const requestBody = { email: formParameters.email, - firstname: formParameters.firstName || '', - lastname: '', - address: '', - zip: '', - city: '', - phone: '', - company: '', - birthday: new Date().toISOString().split('T')[0], - tags: finalTags, - external_id: `ext-${formParameters.email}`, - gender: '', + tags: requestedTags, } console.log('📤 Sending subscription request:', { @@ -130,7 +61,6 @@ export const signUp = async (formParameters: SubscribeFormParameters) => { const response = await subscriberApi.post(`/subscribers?subscriber_list_id=${SUBSCRIBER_LIST_ID}`, requestBody) - console.log('✅ Successfully subscribed:', response.data) return response.status === 200 } catch (error: any) { console.error('❌ Error in signUp:', { @@ -143,28 +73,24 @@ export const signUp = async (formParameters: SubscribeFormParameters) => { } } +const newsletterApi = axios.create({ + baseURL: MAKE_NEWSLETTER_API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${Buffer.from(`${MAKE_API_USER}:${MAKE_API_KEY}`).toString('base64')}`, + }, +}) + /** * Distribute a newsletter */ -export const distribute = async (parameters: NewsDistributionParameters) => { +export const distribute = async () => { try { - console.log('🔹 distribute() called with:', parameters) - + const url = `${MAKE_NEWSLETTER_API_BASE_URL}/recurring_actions/${MAKE_NEWSLETTER_ID}/trigger` const requestBody = { - segment_id: SUBSCRIBER_LIST_ID, sender_id: MAKE_API_USER, - scheduled_at: new Date(), } - - console.log('📤 Sending request to newsletter API:', { - url: `/newsletters/${parameters.newsletterId}/send`, - headers: newsletterApi.defaults.headers, - body: requestBody, - }) - - const response = await newsletterApi.post(`/newsletters/${parameters.newsletterId}/send`, requestBody) - - console.log('✅ Success! API response:', response.status, response.data) + const response = await newsletterApi.post(url, requestBody) return response.status === 200 } catch (error: any) { console.error('❌ Error in distribute:', { @@ -173,6 +99,7 @@ export const distribute = async (parameters: NewsDistributionParameters) => { responseStatus: error.response?.status, requestHeaders: error.config?.headers, }) + return false } } From 42d26456e56007b7c406551315b53ef50fcbc5d6 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Tue, 4 Mar 2025 15:36:10 +0100 Subject: [PATCH 6/7] Revert captcha removal #2839 --- web/pages/api/subscribe-form.ts | 50 +++++++++------------------------ 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/web/pages/api/subscribe-form.ts b/web/pages/api/subscribe-form.ts index fae65f062..7df75e1d4 100644 --- a/web/pages/api/subscribe-form.ts +++ b/web/pages/api/subscribe-form.ts @@ -2,41 +2,19 @@ import { signUp } from './subscription' import { NextApiRequest, NextApiResponse } from 'next' import { validateFormRequest } from './forms/validateFormRequest' -export default async function handler( - req: { body: { subscribeFormParamers: any; frcCaptchaSolution: any }; method: string }, - res: { - status: (arg0: number) => { - (): any - new (): any - json: { (arg0: { error?: any; msg?: string }): any; new (): any } - } - }, -) { - console.log('📥 API received request with:', req.body) - - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method Not Allowed' }) - } - - const { subscribeFormParamers, frcCaptchaSolution } = req.body - - // 🛑 Check if subscribeFormParamers is undefined - if (!subscribeFormParamers || !subscribeFormParamers.email) { - console.error('❌ Missing or invalid subscribeFormParamers:', subscribeFormParamers) - return res.status(400).json({ msg: 'Invalid request data' }) - } - - console.log('✅ Valid request data:', subscribeFormParamers) - - try { - const success = await signUp(subscribeFormParamers) - if (!success) throw new Error('Subscription failed') - - return res.status(200).json({ msg: 'Subscription successful' }) - } catch (error) { - console.error('❌ Error in signUp:', (error as Error).message) - return res.status(500).json({ msg: 'Subscription failed', error: (error as Error).message }) +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const result = await validateFormRequest(req, 'subscribe form') + if (result.status !== 200) { + return res.status(result.status).json({ msg: result.message }) } + const data = req.body + await signUp(data.subscribeFormParamers) + .then((isSuccessful) => { + if (!isSuccessful) { + res.status(500).json({ msg: 'Subscribe failed' }) + } else res.status(200).json({ msg: 'Successfully subscribed.' }) + }) + .catch((error) => { + res.status(500).json({ msg: `Subscribe failed ${error}` }) + }) } - - From b103dceb1b046421e47928d4b6fc9c8498f7fb33 Mon Sep 17 00:00:00 2001 From: "Malin J." Date: Tue, 4 Mar 2025 15:38:52 +0100 Subject: [PATCH 7/7] Revert friendlycaptcha removal in form #2839 --- .../topicPages/Form/SubscribeForm.tsx | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/web/pageComponents/topicPages/Form/SubscribeForm.tsx b/web/pageComponents/topicPages/Form/SubscribeForm.tsx index 54e7f0a91..5bcdc77ce 100644 --- a/web/pageComponents/topicPages/Form/SubscribeForm.tsx +++ b/web/pageComponents/topicPages/Form/SubscribeForm.tsx @@ -1,3 +1,4 @@ +import type { SubscribeFormParameters } from '../../../types/index' import { Icon } from '@equinor/eds-core-react' import { useForm, Controller } from 'react-hook-form' import { error_filled } from '@equinor/eds-icons' @@ -14,20 +15,10 @@ type FormValues = { categories: string[] } -export type SubscribeFormParameters = { - firstName: string - email: string - crudeOilAssays?: boolean - generalNews?: boolean - magazineStories?: boolean - stockMarketAnnouncements?: boolean - languageCode: string -} - const SubscribeForm = () => { const router = useRouter() const intl = useIntl() - const [isFriendlyChallengeDone, setIsFriendlyChallengeDone] = useState(process.env.NODE_ENV === 'development') + const [isFriendlyChallengeDone, setIsFriendlyChallengeDone] = useState(false) const [isServerError, setServerError] = useState(false) const [isSuccessfullySubmitted, setSuccessfullySubmitted] = useState(false) @@ -38,11 +29,9 @@ const SubscribeForm = () => { register, setError, formState: { errors, isSubmitting, isSubmitted, isSubmitSuccessful }, - } = useForm({ defaultValues: { firstName: '', email: '', categories: [] } }) + } = useForm({ defaultValues: { firstName: '', email: '', categories: [] } }) const onSubmit = async (data: FormValues, event?: BaseSyntheticEvent) => { - console.log('📩 Form submitted with data:', data) - if (isFriendlyChallengeDone) { const allCategories = data.categories.includes('all') const subscribeFormParamers: SubscribeFormParameters = { @@ -52,30 +41,23 @@ const SubscribeForm = () => { generalNews: allCategories || data.categories.includes('generalNews'), stockMarketAnnouncements: allCategories || data.categories.includes('stockMarketAnnouncements'), magazineStories: allCategories || data.categories.includes('magazineStories'), - languageCode: router.locale === 'en' ? 'en' : 'no', + languageCode: router.locale == 'en' ? 'en' : 'no', } - console.log('📝 Sending API request with:', subscribeFormParamers) - const res = await fetch('/api/subscribe-form', { body: JSON.stringify({ subscribeFormParamers, - frcCaptchaSolution: event?.target?.['frc-captcha-solution']?.value || 'bypass', + frcCaptchaSolution: (event?.target as any)['frc-captcha-solution'].value, }), - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + }, method: 'POST', }) - - console.log('🔹 API response status:', res.status) - - if (!res.ok) { - const errorResponse = await res.json() - console.error('❌ API error response:', errorResponse) - setServerError(true) - } else { - setSuccessfullySubmitted(true) - } + setServerError(res.status != 200) + setSuccessfullySubmitted(res.status == 200) } else { + //@ts-ignore: TODO: types setError('root.notCompletedCaptcha', { type: 'custom', message: intl.formatMessage({ @@ -86,7 +68,6 @@ const SubscribeForm = () => { } } - return ( <> {!isSuccessfullySubmitted && !isServerError && (