feat: add blog_options and rss feed generation

This commit is contained in:
2026-03-15 15:07:34 +01:00
parent 419c8a579c
commit b44868361d
17 changed files with 367 additions and 14 deletions

13
app/server/rss/index.js Normal file
View File

@@ -0,0 +1,13 @@
import { RssGenerator } from "./rssGenerator.js";
let generator = null;
export default (() => {
if (!generator) {
generator = Object.freeze(new RssGenerator({
filePath: '/home/node/rss/rss.xml',
}));
}
return generator;
})();

View File

@@ -0,0 +1,123 @@
import fs from 'node:fs';
export class RssGenerator {
maxItem = 10;
items = [];
channel = {};
constructor(options) {
this.options = options;
}
// TODO: move to pass an option parameter and iterate over the key/values
setChannel (title, description, link, pubDate) {
this.channel.title = title;
this.channel.description = description;
this.channel.link = link;
this.channel.pubDate = pubDate;
return this;
}
addItem (item) {
if (!this.isItemValid(item)) {
console.error('[RssGenerator] addItem: item invalid.', item);
return this;
}
this.items.push(item);
if (this.items.lenght > this.maxItem) {
this.items.shift();
}
return this;
}
isItemValid(item) {
return true;
}
generateFile() {
let data = '';
data += '<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"><channel>';
data += this.#generateChannelInfos();
data += this.#generateItems();
data += '</channel></rss>';
this.#saveFile(data);
return this;
}
#generateChannelInfos() {
let infos = '';
infos += `<title>${this.channel.title}</title>\n`;
infos += `<description>${this.channel.description}</description>\n`;
infos += `<link>${this.channel.link}</link>\n`;
infos += `<pubDate>${RssGenerator.formatDate(this.channel.pubDate)}</pubDate>\n`;
infos += `<atom:link href="${this.channel.link}/rss.xml" rel="self" type="application/rss+xml" />\n`;
return infos;
}
#generateItems() {
let data = '';
for (const item of this.items) {
data += '<item>\n';
data += `<title>${item.title}</title>\n`;
data += `<link>${item.link}</link>\n`;
data += `<guid>${item.link}</guid>\n`;
data += `<description>${item.description}</description>\n`;
data += '</item>\n';
}
return data;
}
static formatDate(date) {
return new Intl.DateTimeFormat("en-GB", {
hour12: false,
weekday: "short",
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZone: "Europe/London",
}).formatToParts(date)
.filter(el => el.type !== "literal")
.reduce((acc, cur) => {
switch (cur.type) {
case "weekday":
acc += `${cur.value}, `;
break;
case "hour":
case "minute":
acc += `${cur.value}:`;
break;
case "timeZoneName":
acc += cur.value;
break;
default:
acc += `${cur.value} `;
break;
}
return acc;
}, '')
.concat(" GMT");
}
#saveFile(data) {
try {
fs.writeFileSync(this.options.filePath, data, { encoding: 'utf8' });
} catch (err) {
console.error('[RssGenerator] saveFile - ERROR');
console.error(err);
}
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>$TITLE</title>
<description>$DESCRIPTION</description>
<link>$LINK</link>
<channel>
</rss>