mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
## Summary Fixes all errors and warnings reported by the [W3C Feed Validation Service](https://validator.w3.org/feed/check.cgi?url=https%3A%2F%2Ffleetdm.com%2Frss%2Farticles) for the `/rss/articles` endpoint (and all other `/rss/:categoryName` endpoints). ## Changes Only one file modified: `website/api/controllers/download-rss-feed.js` ### Errors fixed - **`lastBuildDate` not RFC-822 format**: Changed from `new Date(Date.now())` (which produces JS `toString()` format like `Thu Mar 19 2026 14:45:30 GMT+0000 (Coordinated Universal Time)`) to `new Date().toUTCString()` (which produces RFC-822 format like `Thu, 19 Mar 2026 14:45:30 GMT`) - **`pubDate` not RFC-822 format** (431 occurrences): Changed from `.toJSON()` (ISO 8601) to `.toUTCString()` (RFC-822) - **Missing channel `<link>` element**: Added `<link>` element at the channel level pointing to the category page ### Warnings fixed - **Missing `guid` on items** (431 occurrences): Added `<guid isPermaLink="true">` to each item using the article's permalink URL - **Missing `atom:link` with `rel="self"`**: Added `xmlns:atom` namespace to the `<rss>` element and an `<atom:link href="..." rel="self" type="application/rss+xml"/>` element in the channel ### Additional fix - Fixed a minor bug where the image `<link>` URL was missing a `/` separator between the domain and category name (`fleetdm.comarticles` → `fleetdm.com/articles`) ### Not addressed - The "Invalid HTML: Named entity expected" warning about `'` entities in descriptions. This is produced by Lodash's `_.escape()` which correctly escapes apostrophes for XML content. The `'` entity is valid XML — the validator flags it only in an HTML parsing context, and it does not affect feed validity or reader interoperability. --- Built for [Brock Walters](https://fleetdm.slack.com/archives/C097P4TAPRR/p1773932018039599) by [Kilo for Slack](https://kilo.ai/features/slack-integration) --------- Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> Co-authored-by: Eric <[email protected]>
145 lines
5.4 KiB
JavaScript
Vendored
145 lines
5.4 KiB
JavaScript
Vendored
module.exports = {
|
|
|
|
|
|
friendlyName: 'Download rss feed',
|
|
|
|
|
|
description: 'Generate and return an RSS feed for a category of Fleet\'s articles',
|
|
|
|
|
|
inputs: {
|
|
|
|
categoryName: {
|
|
type: 'string',
|
|
required: true,
|
|
isIn: [
|
|
'success-stories',
|
|
'securing',
|
|
'releases',
|
|
'engineering',
|
|
'guides',
|
|
'announcements',
|
|
'deploy',
|
|
'podcasts',
|
|
'report',
|
|
'articles',
|
|
],
|
|
}
|
|
|
|
},
|
|
|
|
|
|
exits: {
|
|
success: { outputFriendlyName: 'RSS feed XML', outputType: 'string' },
|
|
badConfig: { responseType: 'badConfig' },
|
|
},
|
|
|
|
|
|
fn: async function ({categoryName}) {
|
|
|
|
if (!_.isObject(sails.config.builtStaticContent)) {
|
|
throw {badConfig: 'builtStaticContent'};
|
|
} else if (!_.isArray(sails.config.builtStaticContent.markdownPages)) {
|
|
throw {badConfig: 'builtStaticContent.markdownPages'};
|
|
}
|
|
|
|
// Start building the rss feed
|
|
let rssFeedXml = '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel>';
|
|
|
|
// Build the description and title for this RSS feed.
|
|
let articleCategoryTitle = '';
|
|
let categoryDescription = '';
|
|
switch(categoryName) {
|
|
case 'success-stories':
|
|
articleCategoryTitle = 'Success stories | Fleet blog';
|
|
categoryDescription = 'Read about how others are using Fleet and osquery.';
|
|
break;
|
|
case 'securing':
|
|
articleCategoryTitle = 'Security | Fleet blog';
|
|
categoryDescription = 'Learn more about how we secure Fleet.';
|
|
break;
|
|
case 'releases':
|
|
articleCategoryTitle = 'Releases | Fleet blog';
|
|
categoryDescription = 'Read about the latest release of Fleet.';
|
|
break;
|
|
case 'engineering':
|
|
articleCategoryTitle = 'Engineering | Fleet blog';
|
|
categoryDescription = 'Read about engineering at Fleet and beyond.';
|
|
break;
|
|
case 'guides':
|
|
articleCategoryTitle = 'Guides | Fleet blog';
|
|
categoryDescription = 'Learn more about how to use Fleet to accomplish your goals.';
|
|
break;
|
|
case 'announcements':
|
|
articleCategoryTitle = 'Announcements | Fleet blog';
|
|
categoryDescription = 'The latest news from Fleet.';
|
|
break;
|
|
case 'deploy':
|
|
articleCategoryTitle = 'Deployment guides | Fleet blog';
|
|
categoryDescription = 'Learn more about how to deploy Fleet.';
|
|
break;
|
|
case 'podcasts':
|
|
articleCategoryTitle = 'Podcasts | Fleet blog';
|
|
categoryDescription = 'Listen to the Future of Device Management podcast';
|
|
break;
|
|
case 'report':
|
|
articleCategoryTitle = 'Reports | Fleet blog';
|
|
categoryDescription = '';
|
|
break;
|
|
case 'articles':
|
|
articleCategoryTitle = 'Fleet blog | Fleet';
|
|
categoryDescription = 'Read all articles from Fleet\'s blog.';
|
|
}
|
|
|
|
let rssFeedTitle = `<title>${_.escape(articleCategoryTitle)}</title>`;
|
|
let rssFeedDescription = `<description>${_.escape(categoryDescription)}</description>`;
|
|
let rssFeedLink = `<link>${_.escape('https://fleetdm.com/'+categoryName)}</link>`;
|
|
let rssFeedAtomLink = `<atom:link href="${_.escape('https://fleetdm.com/rss/'+categoryName)}" rel="self" type="application/rss+xml"/>`;
|
|
let rsslastBuildDate = `<lastBuildDate>${_.escape(new Date().toUTCString())}</lastBuildDate>`;
|
|
let rssFeedImage = `<image><link>${_.escape('https://fleetdm.com/'+categoryName)}</link><title>${_.escape(articleCategoryTitle)}</title><url>${_.escape('https://fleetdm.com/images/[email protected]')}</url></image>`;
|
|
|
|
rssFeedXml += `${rssFeedTitle}${rssFeedDescription}${rssFeedLink}${rssFeedAtomLink}${rsslastBuildDate}${rssFeedImage}`;
|
|
|
|
|
|
// Determine the subset of articles that will be used to squirt out an XML string.
|
|
let articlesToAddToFeed = [];
|
|
if (categoryName === 'articles') {
|
|
// If the category is `articles` we'll build a rss feed that contains all articles
|
|
articlesToAddToFeed = sails.config.builtStaticContent.markdownPages.filter((page)=>{
|
|
if(_.startsWith(page.htmlId, 'articles')) {
|
|
return page;
|
|
}
|
|
});//∞
|
|
} else {
|
|
// If the user requested a specific category, we'll only build a feed with articles in that category
|
|
articlesToAddToFeed = sails.config.builtStaticContent.markdownPages.filter((page)=>{
|
|
if(_.startsWith(page.url, '/'+categoryName)) {
|
|
return page;
|
|
}
|
|
});//∞
|
|
}
|
|
articlesToAddToFeed = _.sortByOrder(articlesToAddToFeed, 'meta.publishedOn', 'ASC');
|
|
// Iterate through the filtered array of articles, adding <item> elements for each article.
|
|
for (let pageInfo of articlesToAddToFeed) {
|
|
let articleUrl = 'https://fleetdm.com'+pageInfo.url;
|
|
let rssItemTitle = `<title>${_.escape(pageInfo.meta.articleTitle)}</title>`;
|
|
let rssItemDescription = `<description>${_.escape(pageInfo.meta.description)}</description>`;
|
|
let rssItemLink = `<link>${_.escape(articleUrl)}</link>`;
|
|
let rssItemGuid = `<guid isPermaLink="true">${_.escape(articleUrl)}</guid>`;
|
|
let rssItemPublishDate = `<pubDate>${_.escape(new Date(pageInfo.meta.publishedOn).toUTCString())}</pubDate>`;
|
|
// Add the article to the feed.
|
|
rssFeedXml += `<item>${rssItemTitle}${rssItemDescription}${rssItemLink}${rssItemGuid}${rssItemPublishDate}</item>`;
|
|
}
|
|
|
|
rssFeedXml += `</channel></rss>`;
|
|
|
|
// Set the response type
|
|
this.res.type('text/xml');
|
|
|
|
// Return the generated RSS feed
|
|
return rssFeedXml;
|
|
|
|
}
|
|
|
|
|
|
};
|