From 95d488887642c9cb103165143ed96ffab07c4c11 Mon Sep 17 00:00:00 2001
From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com>
Date: Fri, 12 Apr 2024 19:31:56 -0500
Subject: [PATCH] feat: implement rss feed
---
.vscode/settings.json | 1 +
src/Blog/Blog.csproj | 1 +
src/Blog/Program.cs | 69 +++++++++++++++++++++++++++-
test/Blog.Tests/EndToEnd/RssTests.cs | 37 +++++++++++++++
4 files changed, 107 insertions(+), 1 deletion(-)
create mode 100644 test/Blog.Tests/EndToEnd/RssTests.cs
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 035fb60..8e6eda8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,6 +2,7 @@
"cSpell.words": [
"Antiforgery",
"blazor",
+ "Entitize",
"Hsts",
"Markdig",
"msedge"
diff --git a/src/Blog/Blog.csproj b/src/Blog/Blog.csproj
index 49fbe70..3d300cd 100644
--- a/src/Blog/Blog.csproj
+++ b/src/Blog/Blog.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Blog/Program.cs b/src/Blog/Program.cs
index 0ee5596..79f17f8 100644
--- a/src/Blog/Program.cs
+++ b/src/Blog/Program.cs
@@ -1,3 +1,8 @@
+using System.ServiceModel.Syndication;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureOptions();
@@ -21,11 +26,73 @@ app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
+app.UseStatusCodePagesWithRedirects("/Error/{0}");
+
+app
+ .MapGet("/rss", async (HttpContext context, IPostService postService) =>
+ {
+ var req = context.Request;
+ var url = $"{req.Scheme}://{req.Host}{req.PathBase}";
+
+ var posts = await postService.GetPostsAsync();
+
+ var items = posts.Select(post =>
+ {
+ var uri = new Uri($"{url}/{post.Slug}");
+
+ var item = new SyndicationItem(
+ post.Title,
+ post.Lead,
+ uri,
+ post.Slug,
+ post.PublishedAt
+ );
+
+ return item;
+ });
+
+ var feed = new SyndicationFeed(
+ "journal",
+ "A blog by Stevan Freeborn",
+ new Uri(url)
+ )
+ {
+ Items = items,
+ };
+
+ XNamespace atom = "http://www.w3.org/2005/Atom";
+ feed.ElementExtensions.Add(
+ new XElement(atom + "link",
+ new XAttribute("href", url + "/rss"),
+ new XAttribute("rel", "self"),
+ new XAttribute("type", "application/rss+xml")
+ )
+ );
+
+ var settings = new XmlWriterSettings
+ {
+ Encoding = Encoding.UTF8,
+ NewLineHandling = NewLineHandling.Entitize,
+ Indent = true,
+ Async = true,
+ };
+
+ using var stream = new MemoryStream();
+ using var writer = XmlWriter.Create(stream, settings);
+
+ var rssFormatter = new Rss20FeedFormatter(feed, false);
+ rssFormatter.WriteTo(writer);
+ await writer.FlushAsync();
+
+ return Results.File(stream.ToArray(), "application/xml");
+ })
+ .WithDisplayName("RSS Feed")
+ .WithDescription("RSS feed for the blog");
+
app
.MapRazorComponents()
.AddInteractiveServerRenderMode();
-app.UseStatusCodePagesWithRedirects("/Error/{0}");
app.Run();
diff --git a/test/Blog.Tests/EndToEnd/RssTests.cs b/test/Blog.Tests/EndToEnd/RssTests.cs
new file mode 100644
index 0000000..8493f4d
--- /dev/null
+++ b/test/Blog.Tests/EndToEnd/RssTests.cs
@@ -0,0 +1,37 @@
+using System.ServiceModel.Syndication;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Blog.Tests.EndToEnd;
+
+[TestFixture]
+public class RssTests : BlogTest
+{
+ [Test]
+ public async Task Task_Rss_WhenFetched_ItShouldReturnRssFeed()
+ {
+ var response = await Page.APIRequest.GetAsync("/rss");
+ response.Status.Should().Be((int)HttpStatusCode.OK);
+
+ var xml = await response.BodyAsync();
+
+ var streamReader = new StreamReader(
+ new MemoryStream(xml),
+ Encoding.UTF8
+ );
+
+
+ var settings = new XmlReaderSettings
+ {
+ Async = true
+ };
+
+ using var xmlReader = XmlReader.Create(streamReader, settings);
+ var feed = SyndicationFeed.Load(xmlReader);
+
+ feed.Title.Text.Should().Be("journal");
+ feed.Description.Text.Should().Be("A blog by Stevan Freeborn");
+ feed.Items.Should().HaveCount(2);
+ }
+}
\ No newline at end of file