From f7e6f42216c3cbeab9add475a61bb407c6be3519 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 30 Nov 2023 13:12:45 +0100 Subject: refactor(pages): refine RSS feed * add favicon * add item categories * add item image * add item author * add item update date (it seems `date` is used as update date by Feed) * change copyright format * fix ESlint errors * fix atom and json links --- .cspell/project-words.txt | 1 + next.config.js | 5 +++++ src/pages/atom.xml.ts | 22 ++++++++++++++++++++++ src/pages/atom.xml.tsx | 24 ------------------------ src/pages/feed.json.ts | 22 ++++++++++++++++++++++ src/pages/feed.json.tsx | 24 ------------------------ src/pages/feed.xml.ts | 22 ++++++++++++++++++++++ src/pages/feed.xml.tsx | 24 ------------------------ src/utils/helpers/rss.test.ts | 35 +++++++++++++++++++++++++++++++++++ src/utils/helpers/rss.ts | 31 ++++++++++++++++++++++++------- 10 files changed, 131 insertions(+), 79 deletions(-) create mode 100644 src/pages/atom.xml.ts delete mode 100644 src/pages/atom.xml.tsx create mode 100644 src/pages/feed.json.ts delete mode 100644 src/pages/feed.json.tsx create mode 100644 src/pages/feed.xml.ts delete mode 100644 src/pages/feed.xml.tsx create mode 100644 src/utils/helpers/rss.test.ts diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt index 532871a..869310d 100644 --- a/.cspell/project-words.txt +++ b/.cspell/project-words.txt @@ -15,6 +15,7 @@ Gitlab hexcode Jamstack LINKEDIN +maxage nextjs nosniff postbuild diff --git a/next.config.js b/next.config.js index 82f4fe4..fada5f4 100644 --- a/next.config.js +++ b/next.config.js @@ -115,6 +115,11 @@ const nextConfig = { destination: '/feed.xml', permanent: true, }, + { + source: '/feed/atom', + destination: '/atom.xml', + permanent: true, + }, ]; }, sassOptions: { diff --git a/src/pages/atom.xml.ts b/src/pages/atom.xml.ts new file mode 100644 index 0000000..21e2e96 --- /dev/null +++ b/src/pages/atom.xml.ts @@ -0,0 +1,22 @@ +import type { GetServerSideProps } from 'next'; +import { generateFeed } from '../utils/helpers'; + +const Feed = () => null; + +export const getServerSideProps: GetServerSideProps = async ({ res }) => { + const feed = await generateFeed(); + + res.setHeader( + 'Cache-Control', + 'public, s-maxage=600, stale-while-revalidate=59' + ); + res.setHeader('Content-Type', 'text/xml'); + res.write(feed.atom1()); + res.end(); + + return { + props: {}, + }; +}; + +export default Feed; diff --git a/src/pages/atom.xml.tsx b/src/pages/atom.xml.tsx deleted file mode 100644 index 4515fdd..0000000 --- a/src/pages/atom.xml.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { GetServerSideProps } from 'next'; -import { generateFeed } from '../utils/helpers'; - -const Feed = () => null; - -export const getServerSideProps: GetServerSideProps = async ({ res }) => { - const feed = await generateFeed(); - - if (res) { - res.setHeader( - 'Cache-Control', - 'public, s-maxage=600, stale-while-revalidate=59' - ); - res.setHeader('Content-Type', 'text/xml'); - res.write(`${feed.atom1()}`); - res.end(); - } - - return { - props: {}, - }; -}; - -export default Feed; diff --git a/src/pages/feed.json.ts b/src/pages/feed.json.ts new file mode 100644 index 0000000..e155022 --- /dev/null +++ b/src/pages/feed.json.ts @@ -0,0 +1,22 @@ +import type { GetServerSideProps } from 'next'; +import { generateFeed } from '../utils/helpers'; + +const Feed = () => null; + +export const getServerSideProps: GetServerSideProps = async ({ res }) => { + const feed = await generateFeed(); + + res.setHeader( + 'Cache-Control', + 'public, s-maxage=600, stale-while-revalidate=59' + ); + res.setHeader('Content-Type', 'application/json'); + res.write(feed.json1()); + res.end(); + + return { + props: {}, + }; +}; + +export default Feed; diff --git a/src/pages/feed.json.tsx b/src/pages/feed.json.tsx deleted file mode 100644 index 7c77e19..0000000 --- a/src/pages/feed.json.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { GetServerSideProps } from 'next'; -import { generateFeed } from '../utils/helpers'; - -const Feed = () => null; - -export const getServerSideProps: GetServerSideProps = async ({ res }) => { - const feed = await generateFeed(); - - if (res) { - res.setHeader( - 'Cache-Control', - 'public, s-maxage=600, stale-while-revalidate=59' - ); - res.setHeader('Content-Type', 'application/json'); - res.write(`${feed.json1()}`); - res.end(); - } - - return { - props: {}, - }; -}; - -export default Feed; diff --git a/src/pages/feed.xml.ts b/src/pages/feed.xml.ts new file mode 100644 index 0000000..2defaa7 --- /dev/null +++ b/src/pages/feed.xml.ts @@ -0,0 +1,22 @@ +import type { GetServerSideProps } from 'next'; +import { generateFeed } from '../utils/helpers'; + +const Feed = () => null; + +export const getServerSideProps: GetServerSideProps = async ({ res }) => { + const feed = await generateFeed(); + + res.setHeader( + 'Cache-Control', + 'public, s-maxage=600, stale-while-revalidate=59' + ); + res.setHeader('Content-Type', 'text/xml'); + res.write(feed.rss2()); + res.end(); + + return { + props: {}, + }; +}; + +export default Feed; diff --git a/src/pages/feed.xml.tsx b/src/pages/feed.xml.tsx deleted file mode 100644 index 60dcb21..0000000 --- a/src/pages/feed.xml.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { GetServerSideProps } from 'next'; -import { generateFeed } from '../utils/helpers'; - -const Feed = () => null; - -export const getServerSideProps: GetServerSideProps = async ({ res }) => { - const feed = await generateFeed(); - - if (res) { - res.setHeader( - 'Cache-Control', - 'public, s-maxage=600, stale-while-revalidate=59' - ); - res.setHeader('Content-Type', 'text/xml'); - res.write(`${feed.rss2()}`); - res.end(); - } - - return { - props: {}, - }; -}; - -export default Feed; diff --git a/src/utils/helpers/rss.test.ts b/src/utils/helpers/rss.test.ts new file mode 100644 index 0000000..b04f6d3 --- /dev/null +++ b/src/utils/helpers/rss.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from '@jest/globals'; +import { CONFIG } from '../config'; +import { ROUTES } from '../constants'; +import { generateFeed } from './rss'; + +describe('generate-feed', () => { + /* eslint-disable max-statements */ + it('generates a rss feed', async () => { + const feed = await generateFeed(); + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + expect.assertions(10); + + expect(feed.options.author).toStrictEqual({ + name: CONFIG.name, + email: process.env.APP_AUTHOR_EMAIL, + link: CONFIG.url, + }); + expect(feed.options.copyright).toBe( + `\u00A9 ${CONFIG.copyright.startYear} - ${CONFIG.copyright.endYear} ${CONFIG.name} - CC BY SA` + ); + expect(feed.options.description).toBe(process.env.APP_FEED_DESCRIPTION); + expect(feed.options.favicon).toBe(`${CONFIG.url}/favicon.ico`); + expect(feed.options.feedLinks).toStrictEqual({ + json: `${CONFIG.url}${ROUTES.RSS}.json`, + atom: `${CONFIG.url}${ROUTES.RSS}/atom`, + }); + expect(feed.options.generator).toBe('Feed & NextJS'); + expect(feed.options.language).toBe(CONFIG.locales.defaultLocale); + expect(feed.options.link).toBe(CONFIG.url); + expect(feed.options.title).toBe(`${CONFIG.name} | ${CONFIG.baseline}`); + expect(feed.items.length).toBeGreaterThan(0); + }); + /* eslint-enable max-statements */ +}); diff --git a/src/utils/helpers/rss.ts b/src/utils/helpers/rss.ts index 82fa1ee..9e98a38 100644 --- a/src/utils/helpers/rss.ts +++ b/src/utils/helpers/rss.ts @@ -11,7 +11,7 @@ import { ROUTES } from '../constants'; /** * Retrieve the data for all the articles. * - * @returns {Promise} - All the articles. + * @returns {Promise} All the articles. */ const getAllArticles = async (): Promise => { const totalPosts = await fetchPostsCount(); @@ -25,7 +25,7 @@ const getAllArticles = async (): Promise => { /** * Generate a new feed. * - * @returns {Promise} - The feed. + * @returns {Promise} The feed. */ export const generateFeed = async (): Promise => { const author = { @@ -33,15 +33,16 @@ export const generateFeed = async (): Promise => { email: process.env.APP_AUTHOR_EMAIL, link: CONFIG.url, }; - const copyright = `${CONFIG.name} CC BY SA ${CONFIG.copyright.startYear} - ${CONFIG.copyright.endYear}`; + const copyright = `\u00A9 ${CONFIG.copyright.startYear} - ${CONFIG.copyright.endYear} ${CONFIG.name} - CC BY SA`; const title = `${CONFIG.name} | ${CONFIG.baseline}`; const feed = new Feed({ author, copyright, description: process.env.APP_FEED_DESCRIPTION, + favicon: `${CONFIG.url}/favicon.ico`, feedLinks: { - json: `${CONFIG.url}${ROUTES.RSS}/json`, + json: `${CONFIG.url}${ROUTES.RSS}.json`, atom: `${CONFIG.url}${ROUTES.RSS}/atom`, }, generator: 'Feed & NextJS', @@ -53,16 +54,32 @@ export const generateFeed = async (): Promise => { const articles = await getAllArticles(); - articles.forEach((article) => { + for (const article of articles) { feed.addItem({ + author: [author], + category: article.meta.thematics?.map((thematic) => { + return { + domain: `${CONFIG.url}${thematic.url}`, + name: thematic.name, + }; + }), content: article.intro, - date: new Date(article.meta.dates.publication), + copyright, + date: new Date( + article.meta.dates.update ?? article.meta.dates.publication + ), description: article.intro, id: `${article.id}`, + image: article.meta.cover + ? { + url: article.meta.cover.src, + } + : undefined, link: `${CONFIG.url}${ROUTES.ARTICLE}/${article.slug}`, + published: new Date(article.meta.dates.publication), title: article.title, }); - }); + } return feed; }; -- cgit v1.2.3