Openstatus www.openstatus.dev

feat: rss/atom support on the status pages (#1149)

* feat: add rss and atom feeds for the status pages

* fix: fix potential null date case

* chore: add cache time config

* fix: fix lint issues

* ci: apply automated fixes

---------

Co-authored-by: Washington <22279738+washingtonbr@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

Washington Pires
Washington
autofix-ci[bot]
and committed by
GitHub
3957ae1f 5bf9ad05

+86
+86
apps/web/src/app/status-page/[domain]/feed/[type]/route.ts
··· 1 + import { statusDict } from "@/data/incidents-dictionary"; 2 + import { api } from "@/trpc/server"; 3 + import { Feed } from "feed"; 4 + import { notFound } from "next/navigation"; 5 + import { getBaseUrl } from "../../utils"; 6 + 7 + export const revalidate = 60; 8 + 9 + export async function GET( 10 + _request: Request, 11 + props: { params: Promise<{ domain: string; type: string }> }, 12 + ) { 13 + const { domain, type } = await props.params; 14 + if (!["rss", "atom"].includes(type)) return notFound(); 15 + 16 + const page = await api.page.getPageBySlug.query({ slug: domain }); 17 + if (!page) return notFound(); 18 + 19 + const baseUrl = getBaseUrl({ 20 + slug: page.slug, 21 + customDomain: page.customDomain, 22 + }); 23 + 24 + const feed = new Feed({ 25 + id: `${baseUrl}/feed/${type}`, 26 + title: page.title, 27 + description: page.description, 28 + generator: "OpenStatus - Status Page updates", 29 + feedLinks: { 30 + rss: `${baseUrl}/feed/rss`, 31 + atom: `${baseUrl}/feed/atom`, 32 + }, 33 + link: "https://www.openstatus.dev", 34 + author: { 35 + name: "OpenStatus Team", 36 + email: "ping@openstatus.dev", 37 + link: "https://openstatus.dev", 38 + }, 39 + copyright: `Copyright ${new Date().getFullYear().toString()}, OpenStatus`, 40 + language: "en-US", 41 + updated: new Date(), 42 + ttl: 60, 43 + }); 44 + 45 + for (const maintenance of page.maintenances) { 46 + const maintenanceUrl = `${baseUrl}/maintenances/${maintenance.id}`; 47 + feed.addItem({ 48 + id: maintenanceUrl, 49 + title: `Maintenance - ${maintenance.title}`, 50 + link: maintenanceUrl, 51 + description: maintenance.message, 52 + date: maintenance.updatedAt ?? maintenance.createdAt ?? new Date(), 53 + }); 54 + } 55 + 56 + for (const statusReport of page.statusReports) { 57 + const statusReportUrl = `${baseUrl}/reports/${statusReport.id}`; 58 + const status = statusDict[statusReport.status].label; 59 + const statusReportUpdates = statusReport.statusReportUpdates 60 + .map((update) => { 61 + const updateStatus = statusDict[update.status].label; 62 + return `${updateStatus}: ${update.message}.`; 63 + }) 64 + .join("\n\n"); 65 + 66 + feed.addItem({ 67 + id: statusReportUrl, 68 + title: `${status} - ${statusReport.title}`, 69 + link: statusReportUrl, 70 + description: statusReportUpdates, 71 + date: statusReport.updatedAt ?? statusReport.createdAt ?? new Date(), 72 + }); 73 + } 74 + 75 + feed.items.sort( 76 + (a, b) => (a.date as Date).getTime() - (b.date as Date).getTime(), 77 + ); 78 + 79 + const res = type === "atom" ? feed.atom1() : feed.rss2(); 80 + 81 + return new Response(res, { 82 + headers: { 83 + "Content-Type": "application/xml; charset=utf-8", 84 + }, 85 + }); 86 + }