From fb4d9fa0aa842151bc043a2b4f405701a5a65c8b Mon Sep 17 00:00:00 2001 From: pancakes Date: Tue, 16 Sep 2025 13:50:08 +1000 Subject: [PATCH] Add Atom 1.0 feed syndication --- PancakesWeb/Controllers/FeedController.cs | 48 ++++++++++++++++++ PancakesWeb/Data/Button.cs | 6 +++ PancakesWeb/PancakesWeb.csproj | 1 + PancakesWeb/Program.cs | 2 + .../wwwroot/imgs/buttons/valid_atom.webp | Bin 0 -> 2060 bytes 5 files changed, 57 insertions(+) create mode 100644 PancakesWeb/Controllers/FeedController.cs create mode 100644 PancakesWeb/wwwroot/imgs/buttons/valid_atom.webp diff --git a/PancakesWeb/Controllers/FeedController.cs b/PancakesWeb/Controllers/FeedController.cs new file mode 100644 index 0000000..ee35304 --- /dev/null +++ b/PancakesWeb/Controllers/FeedController.cs @@ -0,0 +1,48 @@ +using System.Net.Mime; +using System.ServiceModel.Syndication; +using System.Xml; +using Microsoft.AspNetCore.Mvc; +using PancakesWeb.Components.UI; + +namespace PancakesWeb.Controllers; + +[ApiController] +public class FeedController : ControllerBase +{ + [HttpGet("/feed.atom")] + [Produces("application/atom+xml")] + public ContentResult GetAtomFeed() + { + var feed = new SyndicationFeed + { + Title = new TextSyndicationContent("pancakes' blog"), + Description = new TextSyndicationContent("🐈‍⬛"), + Id = "https://pancakes.gay/feed.atom", + BaseUri = new Uri("https://pancakes.gay/feed.atom"), + Authors = { new SyndicationPerson("p@pancakes.gay", "pancakes", "https://pancakes.gay") }, + Items = BlogPosts.Posts.OrderByDescending(post => post.Published).Select(post => + new SyndicationItem(post.Title, SyndicationContent.CreateHtmlContent(post.Content), + new Uri($"https://pancakes.gay/{post.Slug}"), $"https://pancakes.gay/{post.Slug}", + new DateTimeOffset(post.Edited ?? post.Published)) + { + Copyright = post.Footer is BlogPosts.PostFooter.CcBy + ? new TextSyndicationContent( + $"{post.Title} © {post.Published.Year} by pancakes is licensed under CC BY 4.0") + : null, + PublishDate = new DateTimeOffset(post.Published), + Summary = new TextSyndicationContent(post.Description) + }), + Links = + { + SyndicationLink.CreateSelfLink(new Uri("https://pancakes.gay/feed.atom")), + SyndicationLink.CreateAlternateLink(new Uri("https://pancakes.gay"), MediaTypeNames.Text.Html), + } + }; + + var stringWriter = new StringWriter(); + var writer = new XmlTextWriter(stringWriter); + feed.SaveAsAtom10(writer); + + return Content(stringWriter.ToString(), "application/atom+xml"); + } +} \ No newline at end of file diff --git a/PancakesWeb/Data/Button.cs b/PancakesWeb/Data/Button.cs index fe50183..442b8e5 100644 --- a/PancakesWeb/Data/Button.cs +++ b/PancakesWeb/Data/Button.cs @@ -187,6 +187,12 @@ public static class Buttons { Image = "wii.webp", Title = "Wii" + }, + new Button + { + Image = "valid_atom.webp", + Title = "Valid Atom 1.0", + Href = "/feed.atom" } ]; } \ No newline at end of file diff --git a/PancakesWeb/PancakesWeb.csproj b/PancakesWeb/PancakesWeb.csproj index 13d4c41..224fc7f 100644 --- a/PancakesWeb/PancakesWeb.csproj +++ b/PancakesWeb/PancakesWeb.csproj @@ -8,6 +8,7 @@ + diff --git a/PancakesWeb/Program.cs b/PancakesWeb/Program.cs index 2f7d3fa..fa321a8 100644 --- a/PancakesWeb/Program.cs +++ b/PancakesWeb/Program.cs @@ -6,6 +6,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddHttpClient(); +builder.Services.AddControllers(); builder.Configuration.AddEnvironmentVariables(); @@ -32,5 +33,6 @@ app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); +app.MapControllers(); app.Run(); \ No newline at end of file diff --git a/PancakesWeb/wwwroot/imgs/buttons/valid_atom.webp b/PancakesWeb/wwwroot/imgs/buttons/valid_atom.webp new file mode 100644 index 0000000000000000000000000000000000000000..2de0455ce7852b3e6ca29ef0026f1cd8c43df2cb GIT binary patch literal 2060 zcmYLKc{r49AAN@G#V92+7?bHuF>jVik|k>+*_VkvQ?^W&i6Z+bBub;QHkQd=!qCXp zM?$vjUb|$;l94P8!^}MIw0zfhU)Oz~^Ei-wAOP4K>RH%XoW=?R0Kgpq zdjOCH0Inj35&!@w63BoY!vYBI1~{C^D`Cugc}sok)6u`BSGsy)BkB4-a0xOI+A~_Z zI{6%{fQWU-b`eLNji@zdiuLQ-#~0FjL}LmKXaqsFuFCF1Cb-udhZ-RrQ5kARgp2 zZq3jjEMq2)L1@N(M+o2sKo>TJ)49C4rn5}^T#w@1(TO16&h9paJhqODZEVBuh zKkq)6+c0aEL#{&jP;cCmc=t8MUyw(`JLw>>=D>t0HblPFk(I9tYgZhCNOj-Snsj;p zcX*Efg*1w+FvOOB>JAJ5I3QBC{wF)? z$QMghpo&52jLIH!L=zQqCLcD%^oEHnkk(+l-;^Jp&(~a|1X1ZDhf=fhN;}4Fa$BB{ z2BXX^0?No&+>h1B9;sAO9hD*U-W|pCRyLRDM6QntrwQ*j;dhdLhm>8Pu_IO>EXiWz<8X?)5Njcin^yx*YJ9&U|)=5UCwp8E~On&Lt#EA z7tKG5f2W>a<%}AZLq$uUt!_h%l?G_r*(cVs%gYne& zQ?;?ORgqiM3ce1Co~$AX@o+@r9El%pt>3>HDXU)pVjh30uT5`k>36PU2E7bF@#a({6GMV45ZglUoc6spf+knQ{WRv-@ z)y0hq!TBfr5ITLUrPVQU1=W!np$hdAL6QLM;8GWXF(>R)k9S!f6xC}97bXU{8$|0M% z)(UTnr3E!*=|HM#i(PcTC7^Ch=UI27MtbBMD2bhwlkDI&uK<{kSC^G9)FempIdZ>K z@n11QNhc7CMFC-6XA% z*?Sq~ZPu9)4q5bwIn@#G*Mq`!w^!W_s2>ZF-=qF3E6A_7jgcpY+#g2bwPRnlsd;p< zmQJ}9fJ`AR5bR**&iX5zaFt8Xd*2T>z}9UNTuWj9$)9(aw3GgJ_s{is3Z3rQ&4xV~a7(GC zK#ENG3LBzNE49aDuJpsEAeNpjP!}!H;`p z7AWJZmkQCKxd&d}dDb0cQQ!K?yAwIQ-hM!SbiS*jCa$ z6YB04={_bU)EfOdyfoO&Ib-^D!L^I~K*O*1$B>3NRaoe)m!2K^sdrUuMa3gl&Q)9- zA4t2l_pTtr?CDU*$;CIW@VU{AN^g3i1ld;#y{^(Q^SNKFnk@tXD~%5i9XH&eey}ca znJ+G%atxEAYfYdFf#{vRi)TIcq9?L#gKLHihLQhJWp?M1g!C-^k|FIS!E)h2Eew{4 zO1{#}mAQQZ`&rHMhR2hA+P;WcLg4!~4znuF?UMi}202dN-KB_|aq6;*dNDgnx zX{<7ziMuV@jZY2im+Eo*>#P8?sWrVgBb7p4?UE&79p#j~9$wd19^CQE2o!!5|3Q?a zv9^Fpd^w=(YkX$Lp!sH9}-@&kJzya0r&|GY2=-!HV|a`+$a#N`TqJUm>!kLUk> ze9W$2es18ebDsPD%=sVk13-cRu#2lGtEwnN9{=VLZUR90qpK(@Lnyx!K@b3VD8Cc2 O+&{d3_^(#y&i?{^O2C=` literal 0 HcmV?d00001