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/pages/api/news-letter-distribution.ts b/web/pages/api/news-letter-distribution.ts index a13436265..3aa7980e0 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,16 @@ export const config = { }, } +export type NewsDistributionParameters = { + 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) @@ -36,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' @@ -83,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 3bbbf51f3..0ec3027f9 100644 --- a/web/pages/api/subscription.ts +++ b/web/pages/api/subscription.ts @@ -1,120 +1,105 @@ -import soapRequest from 'easy-soap-request' -import * as xml2js from 'xml2js' -import { LoginResult, SubscribeFormParameters, NewsDistributionParameters } from '../../types/index' -//import { appInsights } from '../../common' +import axios from 'axios' -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', +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 } +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 + 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 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')}`, + }, +}) - 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 -} +/** + * Subscribe a user using subscriber_list_id and tags + */ +export const signUp = async (formParameters: SubscribeFormParameters) => { + try { + const requestedTags: string[] = [] + if (formParameters.stockMarketAnnouncements) requestedTags.push('Stock') + if (formParameters.generalNews) requestedTags.push('Company') + if (formParameters.crudeOilAssays) requestedTags.push('Crude') + if (formParameters.magazineStories) requestedTags.push('Magazine') -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 + const requestBody = { + email: formParameters.email, + tags: requestedTags, } - }) - return response.statusCode == 200 -} + console.log('📤 Sending subscription request:', { + url: `/subscribers?subscriber_list_id=${SUBSCRIBER_LIST_ID}`, + headers: subscriberApi.defaults.headers, + body: requestBody, + }) -export const signUp = async (formParameters: SubscribeFormParameters) => { - const loginResult = await authenticate() - if (loginResult.apiSecret != '' && loginResult.instId != '') return createSignUpRequest(loginResult, formParameters) - else return false -} + const response = await subscriberApi.post(`/subscribers?subscriber_list_id=${SUBSCRIBER_LIST_ID}`, requestBody) -export const distribute = async (parameters: NewsDistributionParameters) => { - const loginResult = await authenticate() - if (loginResult.apiSecret != '' && loginResult.instId != '') { - return createDistributeRequest(loginResult, parameters) - } else return false + 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 + } } -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 +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 () => { + try { + const url = `${MAKE_NEWSLETTER_API_BASE_URL}/recurring_actions/${MAKE_NEWSLETTER_ID}/trigger` + const requestBody = { + sender_id: MAKE_API_USER, + } + const response = await newsletterApi.post(url, requestBody) + 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: