Merge pull request #5 from StevanFreeborn/stevanfreeborn/feat/add-rss-feed
feat: add rss feed
This commit is contained in:
Vendored
+1
@@ -2,6 +2,7 @@
|
|||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Antiforgery",
|
"Antiforgery",
|
||||||
"blazor",
|
"blazor",
|
||||||
|
"Entitize",
|
||||||
"Hsts",
|
"Hsts",
|
||||||
"Markdig",
|
"Markdig",
|
||||||
"msedge"
|
"msedge"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Markdig" Version="0.36.2" />
|
<PackageReference Include="Markdig" Version="0.36.2" />
|
||||||
|
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.2" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.2" />
|
||||||
<PackageReference Include="WebStoating.Markdig.Prism" Version="1.0.0" />
|
<PackageReference Include="WebStoating.Markdig.Prism" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
global using System.IO.Abstractions;
|
global using System.IO.Abstractions;
|
||||||
|
global using System.ServiceModel.Syndication;
|
||||||
|
global using System.Text;
|
||||||
global using System.Text.Json;
|
global using System.Text.Json;
|
||||||
|
global using System.Xml;
|
||||||
|
global using System.Xml.Linq;
|
||||||
|
|
||||||
global using Blog.Components;
|
global using Blog.Components;
|
||||||
global using Blog.Posts;
|
global using Blog.Posts;
|
||||||
|
|||||||
+63
-1
@@ -21,11 +21,73 @@ app.UseHttpsRedirection();
|
|||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseAntiforgery();
|
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
|
app
|
||||||
.MapRazorComponents<App>()
|
.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
app.UseStatusCodePagesWithRedirects("/Error/{0}");
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class BlogTest : PageTest
|
|||||||
{
|
{
|
||||||
await Context.Tracing.StopAsync(new()
|
await Context.Tracing.StopAsync(new()
|
||||||
{
|
{
|
||||||
Path = Path.Combine(
|
Path = Path.Combine(
|
||||||
TestContext.CurrentContext.WorkDirectory,
|
TestContext.CurrentContext.WorkDirectory,
|
||||||
"playwright-traces",
|
"playwright-traces",
|
||||||
$"{TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.Name}.zip"
|
$"{TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.Name}.zip"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
global using System.IO.Abstractions;
|
global using System.IO.Abstractions;
|
||||||
global using System.Net;
|
global using System.Net;
|
||||||
|
global using System.ServiceModel.Syndication;
|
||||||
|
global using System.Text;
|
||||||
|
global using System.Xml;
|
||||||
|
|
||||||
global using Blog.Posts;
|
global using Blog.Posts;
|
||||||
|
|
||||||
@@ -10,10 +13,10 @@ global using Microsoft.AspNetCore.Hosting.Server;
|
|||||||
global using Microsoft.AspNetCore.Mvc.Testing;
|
global using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
global using Microsoft.Extensions.DependencyInjection;
|
global using Microsoft.Extensions.DependencyInjection;
|
||||||
global using Microsoft.Extensions.Hosting;
|
global using Microsoft.Extensions.Hosting;
|
||||||
|
global using Microsoft.Extensions.Logging;
|
||||||
global using Microsoft.Extensions.Options;
|
global using Microsoft.Extensions.Options;
|
||||||
global using Microsoft.Playwright;
|
global using Microsoft.Playwright;
|
||||||
global using Microsoft.Playwright.NUnit;
|
global using Microsoft.Playwright.NUnit;
|
||||||
global using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
global using Moq;
|
global using Moq;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user