diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,4 @@ # GUIX /output/ /var/ +web/e.cash/public/rss.xml diff --git a/web/e.cash/.env b/web/e.cash/.env new file mode 100644 --- /dev/null +++ b/web/e.cash/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_SITE_URL=https://e.cash +NEXT_PUBLIC_STRAPI_URL=https://strapi.fabien.cash diff --git a/web/e.cash/package-lock.json b/web/e.cash/package-lock.json --- a/web/e.cash/package-lock.json +++ b/web/e.cash/package-lock.json @@ -14,6 +14,7 @@ "react": "18.2.0", "react-countdown": "^2.3.5", "react-dom": "18.2.0", + "rss": "^1.2.2", "sharp": "^0.32.4", "styled-components": "^5.3.10" }, @@ -8818,6 +8819,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rss": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", + "integrity": "sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==", + "dependencies": { + "mime-types": "2.1.13", + "xml": "1.0.1" + } + }, + "node_modules/rss/node_modules/mime-db": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "integrity": "sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rss/node_modules/mime-types": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", + "integrity": "sha512-ryBDp1Z/6X90UvjUK3RksH0IBPM137T7cmg4OgD5wQBojlAiUwuok0QeELkim/72EtcYuNlmbkrcGuxj3Kl0YQ==", + "dependencies": { + "mime-db": "~1.25.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -10011,6 +10040,11 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -16529,6 +16563,30 @@ "glob": "^7.1.3" } }, + "rss": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", + "integrity": "sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==", + "requires": { + "mime-types": "2.1.13", + "xml": "1.0.1" + }, + "dependencies": { + "mime-db": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "integrity": "sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w==" + }, + "mime-types": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", + "integrity": "sha512-ryBDp1Z/6X90UvjUK3RksH0IBPM137T7cmg4OgD5wQBojlAiUwuok0QeELkim/72EtcYuNlmbkrcGuxj3Kl0YQ==", + "requires": { + "mime-db": "~1.25.0" + } + } + } + }, "run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -17373,6 +17431,11 @@ "dev": true, "requires": {} }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, "xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/web/e.cash/package.json b/web/e.cash/package.json --- a/web/e.cash/package.json +++ b/web/e.cash/package.json @@ -15,6 +15,7 @@ "react": "18.2.0", "react-countdown": "^2.3.5", "react-dom": "18.2.0", + "rss": "^1.2.2", "sharp": "^0.32.4", "styled-components": "^5.3.10" }, diff --git a/web/e.cash/pages/blog.js b/web/e.cash/pages/blog.js --- a/web/e.cash/pages/blog.js +++ b/web/e.cash/pages/blog.js @@ -18,6 +18,8 @@ DateText, } from '/styles/pages/blog.js'; import { formatTimestamp } from '/data/blog.js'; +import fs from 'fs'; +import RSS from 'rss'; function Blog(props) { const featuredPosts = props.posts.slice(0, 3); @@ -97,6 +99,41 @@ ); } +/** + * Converts JSON blog posts into XML format for RSS feed + * @param {object} posts - the blog posts to be converted to XML format + */ +async function generateRssFeed(posts) { + const feedOptions = { + title: 'eCash Blog', + description: 'Latest eCash news and information', + site_url: process.env.NEXT_PUBLIC_SITE_URL, + feed_url: `${process.env.NEXT_PUBLIC_SITE_URL}/rss.xml`, + image_url: `${process.env.NEXT_PUBLIC_SITE_URL}/images/logos/ecash-logo-primary-horizontal-dark-text.png`, + pubDate: new Date(), + copyright: `©${new Date().getFullYear()}, Bitcoin ABC. All Rights Reserved.`, + }; + + const feed = new RSS(feedOptions); + + posts.map(post => { + feed.item({ + title: post.attributes.title, + description: post.attributes.short_content, + url: `${process.env.NEXT_PUBLIC_SITE_URL}/blog/${post.attributes.slug}`, + date: post.attributes.publishedAt, + enclosure: { + url: + process.env.NEXT_PUBLIC_STRAPI_URL + + post.attributes.image.data.attributes.url, + type: post.attributes.image.data.attributes.mime, + length: post.attributes.image.data.attributes.size, + }, + }); + }); + fs.writeFileSync('./public/rss.xml', feed.xml({ indent: true })); +} + /** * Call function to fetch blog api data and return posts * This only runs at build time, and the build should fail if the api call fails @@ -106,6 +143,8 @@ export async function getStaticProps() { const posts = await getBlogPosts(); const orderedPosts = await sortBlogPostsByDate(posts.props.posts); + await generateRssFeed(orderedPosts); + return { props: { posts: orderedPosts,