diff --git a/src/routes/(marketing)/blog/+page.svelte b/src/routes/(marketing)/blog/+page.svelte index 96921d0..3fa05dd 100644 --- a/src/routes/(marketing)/blog/+page.svelte +++ b/src/routes/(marketing)/blog/+page.svelte @@ -54,8 +54,38 @@ jsonLd={blogJsonLd} /> + + + +
-

Blog

+
+

Blog

+ + + RSS + +
{#each data.posts as post (post.slug)} + + + +
diff --git a/src/routes/(marketing)/blog/rss.xml/+server.js b/src/routes/(marketing)/blog/rss.xml/+server.js new file mode 100644 index 0000000..6ead9b9 --- /dev/null +++ b/src/routes/(marketing)/blog/rss.xml/+server.js @@ -0,0 +1,89 @@ +export const prerender = true + +const SITE_URL = 'https://postguard.eu' +const FEED_URL = `${SITE_URL}/blog/rss.xml` +const FEED_TITLE = 'PostGuard Blog' +const FEED_DESCRIPTION = + 'News, updates, and insights about PostGuard — secure end-to-end encryption for email and files.' + +/** + * Escape XML special characters so user-supplied frontmatter values are safe + * to embed in element text and attributes. + * @param {unknown} value + */ +function escapeXml(value) { + return String(value ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +/** + * Format an ISO date as an RFC 822 string, which is what RSS 2.0 requires. + * @param {string | undefined} iso + */ +function toRfc822(iso) { + const d = iso ? new Date(iso) : new Date() + return Number.isNaN(d.getTime()) + ? new Date().toUTCString() + : d.toUTCString() +} + +export function GET() { + /** @type {Record }>} */ + const postFiles = /** @type {any} */ ( + import.meta.glob('/src/content/blog/*.svx', { eager: true }) + ) + + const posts = Object.entries(postFiles) + .map(([path, mod]) => { + const slug = /** @type {string} */ (path.split('/').pop()).replace( + '.svx', + '' + ) + const metadata = mod?.metadata ?? {} + return { + slug, + title: metadata.title ?? slug, + description: metadata.description ?? '', + date: metadata.date ?? '', + author: metadata.author ?? 'PostGuard', + } + }) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + + const lastBuildDate = toRfc822(posts[0]?.date) + + const items = posts + .map((post) => { + const url = `${SITE_URL}/blog/${post.slug}/` + return ` + ${escapeXml(post.title)} + ${escapeXml(url)} + ${escapeXml(url)} + ${toRfc822(post.date)} + ${escapeXml(post.author)} + ${escapeXml(post.description)} + ` + }) + .join('\n') + + const xml = ` + + + ${escapeXml(FEED_TITLE)} + ${SITE_URL}/blog/ + + ${escapeXml(FEED_DESCRIPTION)} + en + ${lastBuildDate} +${items} + +` + + return new Response(xml, { + headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' }, + }) +}