← back to Stayclaim

src/app/timeline/rss.xml/route.ts

62 lines

import { NextResponse } from 'next/server';
import { getArchiveTimeline } from '@/lib/db';
import { SITE_DOMAIN, SITE_NAME, SITE_TAGLINE } from '@/lib/site';

export const dynamic = 'force-dynamic';
export const runtime = 'nodejs';

/**
 * RSS 2.0 feed of the archive's most recent events.
 * For archivists, GIS folks, and history-twitter.
 */
export async function GET() {
  const events = await getArchiveTimeline({ limit: 50 });
  const base = (process.env.NEXT_PUBLIC_BASE_URL ?? `https://${SITE_DOMAIN}`).replace(/\/$/, '');

  const items = events.map(e => {
    const link = `${base}/address/${encodeURIComponent(e.listing_slug)}`;
    const date = e.valid_from ?? e.recorded_at;
    const pubDate = new Date(date).toUTCString();
    const title = `${e.kind.replace(/_/g, ' ')} · ${e.listing_title}`;
    return [
      '    <item>',
      `      <title>${escapeXml(title)}</title>`,
      `      <link>${escapeXml(link)}</link>`,
      `      <guid isPermaLink="false">pastdoor:event:${escapeXml(e.id)}</guid>`,
      `      <pubDate>${pubDate}</pubDate>`,
      `      <category>${escapeXml(e.kind)}</category>`,
      `      <description>${escapeXml(e.summary)}${e.source_label ? ` — ${escapeXml(e.source_label)}` : ''}</description>`,
      '    </item>',
    ].join('\n');
  });

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>${escapeXml(SITE_NAME)} — archive timeline</title>
    <link>${escapeXml(base + '/timeline')}</link>
    <description>${escapeXml(SITE_TAGLINE)} New events in the archive — permits, sales, archival media.</description>
    <language>en-us</language>
    <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
    <atom:link xmlns:atom="http://www.w3.org/2005/Atom" href="${escapeXml(base + '/timeline/rss.xml')}" rel="self" type="application/rss+xml" />
${items.join('\n')}
  </channel>
</rss>`;

  return new NextResponse(xml, {
    headers: {
      'Content-Type': 'application/rss+xml; charset=utf-8',
      'Cache-Control': 'public, max-age=600, s-maxage=3600',
    },
  });
}

function escapeXml(s: string): string {
  return String(s ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}