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