From 6c23421c9ba535e02e5ba13894732e95c652cfff Mon Sep 17 00:00:00 2001 From: Fernando Lucchesi Date: Tue, 5 Dec 2023 17:04:09 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20RSS=20feed=20for=20latest=20n?= =?UTF-8?q?ews=20#1988?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 1 + web/pages/api/rss/groq.global.ts | 24 ++++++++++ web/pages/api/rss/index.global.ts | 78 +++++++++++++++++++++++++++++++ web/pnpm-lock.yaml | 35 +++++++++++--- 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 web/pages/api/rss/groq.global.ts create mode 100644 web/pages/api/rss/index.global.ts diff --git a/web/package.json b/web/package.json index 70433478a..9b1b8b06e 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "@equinor/eds-tokens": "^0.9.0", "@next/bundle-analyzer": "^12.1.0", "@portabletext/react": "^3.0.0", + "@portabletext/to-html": "^2.0.5", "@portabletext/types": "^2.0.2", "@react-spring/web": "^9.4.5", "@sanity/asset-utils": "^1.3.0", diff --git a/web/pages/api/rss/groq.global.ts b/web/pages/api/rss/groq.global.ts new file mode 100644 index 000000000..65925caab --- /dev/null +++ b/web/pages/api/rss/groq.global.ts @@ -0,0 +1,24 @@ +import { ImageWithCaptionData } from '../../../types' +import { ingressForNewsQuery } from '../../../lib/queries/common/newsSubqueries' +import { publishDateTimeQuery } from '../../../lib/queries/common/publishDateTime' +import { PortableTextBlock } from '@portabletext/types' + +export type LatestNewsType = { + _id: string + slug: string + title: string + publishDateTime: string + hero: ImageWithCaptionData + ingress: PortableTextBlock +} + +export const latestNews = /* groq */ ` + *[_type == "news" && _lang == $lang ][0...20] | order(${publishDateTimeQuery} desc) { + _id, + "slug": slug.current, + title, + "hero": heroImage, + "publishDateTime": ${publishDateTimeQuery}, + ${ingressForNewsQuery}, + } +` diff --git a/web/pages/api/rss/index.global.ts b/web/pages/api/rss/index.global.ts new file mode 100644 index 000000000..eceff9588 --- /dev/null +++ b/web/pages/api/rss/index.global.ts @@ -0,0 +1,78 @@ +import { sanityClient } from '../../../lib/sanity.server' +import { LatestNewsType, latestNews } from './groq.global' +import { defaultComponents, toHTML } from '@portabletext/to-html' +import { urlFor } from '../../../common/helpers/urlFor' +import type { NextApiRequest, NextApiResponse } from 'next' + +const generateRssFeed = async (lang: 'no' | 'en') => { + try { + const map = { + en: { + language: 'en_GB', + title: 'Equinor News', + description: 'Latest news', + }, + no: { + language: 'nb_NO', + title: 'Equinor Nyheter', + description: 'Siste nytt', + }, + } + const languageCode = map[lang].language || 'en_GB' + + const articles: LatestNewsType[] = await sanityClient.fetch(latestNews, { lang: languageCode }) + let rss = ` + + + ${map[lang].title} + ${map[lang].description} + ${lang} + https://www.equinor.com` + + const serializers = { + ...defaultComponents, + marks: { + ...defaultComponents.marks, + sub: ({ children }: { children: string }) => `${children}`, + sup: ({ children }: { children: string }) => `${children}`, + }, + } + + articles.forEach((article) => { + const descriptionHtml = toHTML(article.ingress, { + components: serializers, + onMissingComponent: (e) => String(e), + }) + + const hero = article.hero + const bannerImageUrl = hero?.image?.asset ? urlFor(hero.image).size(560, 280).auto('format').toString() : '' + const imageAlt = hero?.image?.alt ? ` alt="${hero.image.alt}"` : '' + + rss += ` + + ${article.title} + https://equinor.com${article.slug} + https://equinor.com${article.slug} + ${new Date(article.publishDateTime).toUTCString()} +
${descriptionHtml}]]>
+
` + }) + + rss += ` +
+
` + + return rss + } catch (error) { + console.error('Error generating RSS feed:', error) + throw new Error('Failed to generate RSS feed.') + } +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const lang = req.query.lang === 'no' ? 'no' : 'en' // Defaults to 'en' if the lang parameter is not 'no' + const rss = await generateRssFeed(lang) + res.setHeader('Content-Type', 'text/xml') + res.write(rss) + res.end() +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 008870b1a..2b2de0e52 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - dependencies: '@chakra-ui/react': specifier: ^2.4.2 @@ -32,6 +28,9 @@ dependencies: '@portabletext/react': specifier: ^3.0.0 version: 3.0.0(react@18.2.0) + '@portabletext/to-html': + specifier: ^2.0.5 + version: 2.0.5 '@portabletext/types': specifier: ^2.0.2 version: 2.0.2 @@ -4717,6 +4716,14 @@ packages: react: 18.2.0 dev: false + /@portabletext/to-html@2.0.5: + resolution: {integrity: sha512-KkUI6iBuKSzclyKYnwGLiE0asxvW1ZcFmZ7wBslW+nulw78qwpTl3wuK47zf+y0XX7g52d5gw5WlYBAhuSEjSg==} + engines: {node: ^14.13.1 || >=16.0.0} + dependencies: + '@portabletext/toolkit': 2.0.10 + '@portabletext/types': 2.0.8 + dev: false + /@portabletext/toolkit@2.0.1: resolution: {integrity: sha512-vr2SeDFUFV+VmRIyYsHBMimZLiiN0S7qIridt/YLJs3Wm1dI4jsfUam82AQwheSMjvWdBiBZ+Wsjq5HZBG4htw==} engines: {node: ^14.13.1 || >=16.0.0} @@ -4724,11 +4731,23 @@ packages: '@portabletext/types': 2.0.2 dev: false + /@portabletext/toolkit@2.0.10: + resolution: {integrity: sha512-d+F9JvpnMEx7kd6saZ9OWA4U1Iwuokh6TOht7iqkfWU+0ivh9yM4v+b0Kpu+iiPcElicoabhtXol+yTvWJ1jDw==} + engines: {node: ^14.13.1 || >=16.0.0} + dependencies: + '@portabletext/types': 2.0.8 + dev: false + /@portabletext/types@2.0.2: resolution: {integrity: sha512-3xTbSa80G1nODyACY0ilrmPo1PmU1xuoPvfcpzpqSEL0kX01fSW+21PKgS9BcG+GqdG9HK/VxciKWa+nFA7mqg==} engines: {node: ^14.13.1 || >=16.0.0 || >=18.0.0} dev: false + /@portabletext/types@2.0.8: + resolution: {integrity: sha512-eiq9/kMX2bYezS4/kLFk3xNnruCFjCDdw6aYEv5ECHVKkYROiuLd3/AsP5d7tWF3+kPPy6tB0Wq8aqDG/URHGA==} + engines: {node: ^14.13.1 || >=16.0.0 || >=18.0.0} + dev: false + /@react-spring/animated@9.4.5(react@18.2.0): resolution: {integrity: sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==} peerDependencies: @@ -8448,7 +8467,7 @@ packages: dev: false /date-now@1.0.1: - resolution: {integrity: sha512-yiizelQCqYLUEVT4zqYihOW6Ird7Qyc6fD3Pv5xGxk4+Jz0rsB1dMN2KyNV6jgOHYh5K+sPGCSOknQN4Upa3pg==} + resolution: {integrity: sha1-u30IZDjevkGCpIX7PfP7+5nWFTw=} dev: false /debounce@1.0.0: @@ -15750,7 +15769,7 @@ packages: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} /xregexp@2.0.0: - resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==} + resolution: {integrity: sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=} dev: false /xtend@4.0.2: @@ -15841,3 +15860,7 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: true + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false