Implemented simple rss support
This commit is contained in:
parent
d96f946592
commit
03c54249a3
83
Wave/Controllers/RssController.cs
Normal file
83
Wave/Controllers/RssController.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.ServiceModel.Syndication;
|
||||
using System.Xml;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Wave.Data;
|
||||
using Wave.Data.Migrations.postgres;
|
||||
|
||||
namespace Wave.Controllers;
|
||||
|
||||
|
||||
[ApiController]
|
||||
[Route("/[controller]")]
|
||||
public class RssController(IOptions<Customization> customizations, ApplicationDbContext context) : ControllerBase {
|
||||
private ApplicationDbContext Context { get; } = context;
|
||||
private IOptions<Customization> Customizations { get; } = customizations;
|
||||
|
||||
[HttpGet("rss.xml", Name = "RssFeed")]
|
||||
[Produces("application/rss+xml")]
|
||||
[ResponseCache(Duration = 60*15, Location = ResponseCacheLocation.Any)]
|
||||
public async Task<IActionResult> GetRssFeedAsync() {
|
||||
return Ok(await CreateFeedAll("RssFeed"));
|
||||
}
|
||||
[HttpGet("atom.xml", Name = "AtomFeed")]
|
||||
[Produces("application/atom+xml")]
|
||||
[ResponseCache(Duration = 60*15, Location = ResponseCacheLocation.Any)]
|
||||
public async Task<IActionResult> GetAtomFeedAsync() {
|
||||
return Ok(await CreateFeedAll("AtomFeed"));
|
||||
}
|
||||
|
||||
private async Task<SyndicationFeed> CreateFeedAll(string? routeName) {
|
||||
var query = Context.Set<Article>()
|
||||
.Include(a => a.Author).
|
||||
Include(a => a.Categories)
|
||||
.OrderByDescending(a => a.PublishDate)
|
||||
.Take(15);
|
||||
|
||||
var articles = await query.ToListAsync();
|
||||
var date = query.Max(a => a.PublishDate);
|
||||
|
||||
return CreateFeedAsync(articles, date, routeName);
|
||||
}
|
||||
|
||||
private SyndicationFeed CreateFeedAsync(IEnumerable<Article> articles, DateTimeOffset date, string? routeName) {
|
||||
string appName = Customizations.Value.AppName;
|
||||
var host = new Uri($"{Request.Scheme}://{Request.Host}{Request.PathBase}", UriKind.Absolute);
|
||||
var link = new Uri(Url.RouteUrl(routeName, null, Request.Scheme, host.Host) ?? host.AbsoluteUri);
|
||||
|
||||
return new SyndicationFeed(appName, "Feed on " + appName, link, articles
|
||||
.Select(article => {
|
||||
var item = new SyndicationItem(
|
||||
article.Title,
|
||||
new TextSyndicationContent(article.BodyHtml, TextSyndicationContentKind.Html),
|
||||
new Uri(host,
|
||||
$"/{article.PublishDate.Year}/{article.PublishDate.Month:D2}/{article.PublishDate.Day:D2}/{Uri.EscapeDataString(article.Title.ToLowerInvariant()).Replace("-", "+").Replace("%20", "-")}"),
|
||||
new Uri(host, "article/" + article.Id).AbsoluteUri,
|
||||
article.PublishDate) {
|
||||
Authors = {
|
||||
new SyndicationPerson { Name = article.Author.FullName }
|
||||
},
|
||||
LastUpdatedTime = article.LastModified ?? article.PublishDate,
|
||||
PublishDate = article.PublishDate
|
||||
};
|
||||
|
||||
foreach (var category in article.Categories.OrderBy(c => c.Color)) {
|
||||
item.Categories.Add(new SyndicationCategory(category.Name));
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.ToList()) {
|
||||
TimeToLive = TimeSpan.FromMinutes(15),
|
||||
LastUpdatedTime = date,
|
||||
Generator = "Wave",
|
||||
Links = {
|
||||
new SyndicationLink(link) { RelationshipType = "self" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
using Wave.Components.Account;
|
||||
using Wave.Data;
|
||||
using Wave.Services;
|
||||
using Wave.Utilities;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration
|
||||
|
@ -26,7 +27,9 @@
|
|||
.AddEnvironmentVariables("WAVE_");
|
||||
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddControllers(options => {
|
||||
options.OutputFormatters.Add(new SyndicationFeedFormatter());
|
||||
});
|
||||
|
||||
#region Data Protection & Redis
|
||||
|
||||
|
|
46
Wave/Utilities/SyndicationFeedFormatter.cs
Normal file
46
Wave/Utilities/SyndicationFeedFormatter.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.ServiceModel.Syndication;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Wave.Utilities;
|
||||
|
||||
public class SyndicationFeedFormatter : TextOutputFormatter {
|
||||
|
||||
public SyndicationFeedFormatter() {
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/rss+xml"));
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/atom+xml"));
|
||||
|
||||
SupportedEncodings.Add(Encoding.UTF8);
|
||||
}
|
||||
|
||||
protected override bool CanWriteType(Type? type)
|
||||
=> typeof(SyndicationFeed).IsAssignableFrom(type);
|
||||
|
||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) {
|
||||
var httpContext = context.HttpContext;
|
||||
httpContext.Response.Headers.ContentDisposition = "inline";
|
||||
|
||||
var feed = context.Object as SyndicationFeed;
|
||||
|
||||
await using var stringWriter = new StringWriter();
|
||||
await using var rssWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings {
|
||||
Async = true
|
||||
});
|
||||
System.ServiceModel.Syndication.SyndicationFeedFormatter formatter;
|
||||
if (context.ContentType.Value?.StartsWith("application/rss+xml") is true) {
|
||||
formatter = new Rss20FeedFormatter(feed) {
|
||||
SerializeExtensionsAsAtom = false
|
||||
};
|
||||
} else if (context.ContentType.Value?.StartsWith("application/atom+xml") is true) {
|
||||
formatter = new Atom10FeedFormatter(feed);
|
||||
} else {
|
||||
throw new FormatException($"The format {context.ContentType.Value} is not supported.");
|
||||
}
|
||||
formatter.WriteTo(rssWriter);
|
||||
rssWriter.Close();
|
||||
|
||||
await httpContext.Response.WriteAsync(stringWriter.ToString(), selectedEncoding);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
|
||||
<PackageReference Include="Tomlyn.Extensions.Configuration" Version="1.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in a new issue