All files / scripts/rss/render feed.ts

100% Statements 16/16
50% Branches 2/4
100% Functions 1/1
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                            1x           1x   1x 1x     1x       1x                                                         1x       1x 6x         1x 50x                       50x 79x       50x       1x       1x    
/**
 * @module Infrastructure/Rss/Render/Feed
 * @category Intelligence Operations / Supporting Infrastructure
 * @name RSS 2.0 feed builder
 *
 * @description
 * Pure string builder for the full `rss.xml` document — channel header,
 * categories, image, atom:link self-reference, and one `<item>` per
 * article (with hreflang `atom:link` extensions for every alternate
 * language). The lastBuildDate / pubDate / copyright year are derived
 * from the most recent article so output is deterministic.
 *
 * Round-6 split: extracted from `scripts/generate-rss.ts`.
 *
 * @author Hack23 AB (Infrastructure Team)
 * @license Apache-2.0
 */
 
import { getRssArticles } from '../scanner.js';
import { escapeXml } from '../escape.js';
import { hreflangCode } from '../hreflang.js';
 
const BASE_URL = 'https://riksdagsmonitor.com';
 
/**
 * Generate RSS 2.0 XML feed.
 */
export function generateRss(): string {
  console.log('🔨 Generating RSS feed...');
 
  const articles = getRssArticles();
  const now = new Date().toUTCString();
 
  // Use most recent article date for lastBuildDate
  const lastBuildDate = articles.length > 0
    ? new Date(articles[0]!.pubDate).toUTCString()
    : now;
 
  let xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Riksdagsmonitor - Swedish Parliament Intelligence</title>
    <link>${BASE_URL}</link>
    <description>Real-time monitoring, analysis, and intelligence from the Swedish Parliament (Riksdag) and Government. Covering legislative activity, voting patterns, coalition dynamics, and election forecasts.</description>
    <language>en</language>
    <lastBuildDate>${lastBuildDate}</lastBuildDate>
    <pubDate>${lastBuildDate}</pubDate>
    <ttl>60</ttl>
    <copyright>Copyright ${articles.length > 0 ? new Date(articles[0]!.pubDate).getUTCFullYear() : new Date(lastBuildDate).getUTCFullYear()} Hack23 AB. Licensed under Apache-2.0.</copyright>
    <managingEditor>info@hack23.com (Hack23 AB)</managingEditor>
    <webMaster>info@hack23.com (Hack23 AB)</webMaster>
    <generator>Riksdagsmonitor RSS Generator v1.0</generator>
    <docs>https://www.rssboard.org/rss-specification</docs>
    <image>
      <url>https://riksdagsmonitor.com/images/android-chrome-512x512.png</url>
      <title>Riksdagsmonitor</title>
      <link>${BASE_URL}</link>
      <width>144</width>
      <height>144</height>
      <description>Riksdagsmonitor - Swedish Parliament Intelligence Platform</description>
    </image>
    <atom:link href="${BASE_URL}/rss.xml" rel="self" type="application/rss+xml"/>`;
 
  // Add category tags for the channel
  const channelCategories = [
    'Swedish Politics', 'Parliament', 'Riksdag', 'Political Intelligence',
    'Election Analysis', 'Legislative Monitoring',
  ];
  for (const cat of channelCategories) {
    xml += `
    <category>${escapeXml(cat)}</category>`;
  }
 
  // Add items
  for (const article of articles) {
    xml += `
    <item>
      <title>${escapeXml(article.title)}</title>
      <link>${escapeXml(article.link)}</link>
      <description>${escapeXml(article.description)}</description>
      <pubDate>${new Date(article.pubDate).toUTCString()}</pubDate>
      <guid isPermaLink="true">${escapeXml(article.link)}</guid>
      <dc:creator>${escapeXml(article.author)}</dc:creator>
      <category>${escapeXml(article.category)}</category>
      <atom:link href="${escapeXml(article.link)}" rel="alternate" type="text/html" hreflang="en"/>`;
 
    // Add multi-language alternate links
    for (const alt of article.alternateLanguages) {
      xml += `
      <atom:link href="${escapeXml(alt.href)}" rel="alternate" type="text/html" hreflang="${hreflangCode(alt.lang)}"/>`;
    }
 
    xml += `
    </item>`;
  }
 
  xml += `
  </channel>
</rss>`;
 
  return xml;
}