Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/pages/news/rss.xml.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import LRU from "lru-cache";6import { GetServerSideProps } from "next";7import { create as createXML } from "xmlbuilder2";8import type { XMLBuilder } from "xmlbuilder2/lib/interfaces";910// We cache the processed RSS feed for 10 minutes, so that we don't have to recompute it every time.11const cache = new LRU<Channel | "all", any>({12max: 10,13ttl: 10 * 60 * 1000,14});1516import getCustomize from "@cocalc/database/settings/customize";17import { capitalize } from "@cocalc/util/misc";18import { slugURL } from "@cocalc/util/news";19import {20CHANNELS,21CHANNELS_DESCRIPTIONS,22Channel,23} from "@cocalc/util/types/news";24import { renderMarkdown } from "lib/news";25import { getFeedData } from "@cocalc/database/postgres/news";2627// Empty page. getServerSideProps below defines what's going on28export default function RSS() {29return null;30}3132// we have one RSS channel. this populates it with all entries from the database – with the given ordering33async function populateNewsItems(34xml: XMLBuilder,35ch: Channel | "all",36dns: string37): Promise<XMLBuilder> {38for (const n of await getFeedData()) {39const { id, text, title, date, channel } = n;40if (ch != "all" && channel !== ch) continue;41const pubDate: Date =42typeof date === "number" ? new Date(date * 1000) : date;43// URL visible to the user44const url = `https://${dns}/${slugURL(n)}`;45// GUID must be globally unique, not shown to USER46const guid = `https://${dns}/news/${id}`;4748xml49.ele("item")50.ele("title")51.dat(title)52.up()53.ele("link")54.txt(url)55.up()56.ele("description")57.dat(renderMarkdown(text))58.up()59.ele("pubDate")60.txt(pubDate.toUTCString())61.up()62.ele("guid")63.txt(guid)64.up();65}6667return xml;68}6970// render RSS news feed71// check: https://validator.w3.org/feed/check.cgi72// Ref: https://www.w3.org/Protocols/rfc822/ (e.g. that's why it's date.toUTCString())73// There can only be one channel per RSS feed, but we let users filter by channel.74async function getXML(channel?: string): Promise<string> {75const { siteName, dns } = await getCustomize();76if (!dns) throw Error("no dns");7778const ch: Channel | "all" =79channel && CHANNELS.includes(channel as Channel)80? (channel as Channel)81: "all";8283const cached = cache.get(ch);84if (cached) return cached;8586const selfLink = `https://${dns}/news/rss.xml`;87const atom = "http://www.w3.org/2005/Atom";88const descExtra = ch === "all" ? "" : `${CHANNELS_DESCRIPTIONS[ch]}. `;8990const root = createXML({ version: "1.0", encoding: "UTF-8" })91.ele("rss", { version: "2.0" })92.att(atom, "xmlns:atom", atom);9394const xml: XMLBuilder = root95.ele("channel")96.ele("atom:link", {97href: selfLink,98rel: "self",99type: "application/rss+xml",100})101.up()102.ele("title")103.txt(`${siteName} News${ch != "all" ? `– ${capitalize(ch)}` : ""}`)104.up()105.ele("description")106.txt(107`News about ${siteName}. ${descExtra}Also available at https://${dns}/news`108)109.up()110.ele("link")111.txt(selfLink)112.up()113.ele("pubDate")114.txt(new Date().toUTCString())115.up();116117await populateNewsItems(xml, ch, dns);118119const xmlstr = xml.end({ prettyPrint: true });120cache.set(ch, xmlstr);121return xmlstr;122}123124export const getServerSideProps: GetServerSideProps = async ({125query,126res,127}) => {128if (!res) return { props: {} };129const { channel } = query;130const ch = typeof channel === "string" ? channel : undefined;131132try {133res.setHeader("Content-Type", "text/xml");134res.setHeader("Cache-Control", "public, max-age=3600");135res.write(await getXML(ch));136res.end();137} catch (err) {138console.error(err);139res.statusCode = 500;140res.end();141}142143return {144props: {},145};146};147148149