This commit is contained in:
@@ -30,6 +30,7 @@ robots = read("src/TaxBaik.Web/wwwroot/robots.txt")
|
||||
require("Layout = null" in sitemap_view, "sitemap.xml must render without the HTML layout")
|
||||
require("Layout = null" in rss_view, "rss.xml must render without the HTML layout")
|
||||
require("Html.Raw(rssContent.ToString())" in rss_view, "RSS XML must not be HTML-encoded")
|
||||
require("Model.Items" in rss_view, "RSS view must render from the combined feed items")
|
||||
require("StatusCodes.Status308PermanentRedirect" in feed_view, "feed.xml must permanently redirect to rss.xml")
|
||||
require("DateTime.UtcNow:yyyy-MM-dd" not in sitemap_view, "sitemap lastmod must not use the current request date")
|
||||
require("GetAllPublishedPostsAsync" in sitemap_model, "sitemap must page through all published blog posts")
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
rssContent.AppendLine($" <lastBuildDate>{Model.LastBuildDate}</lastBuildDate>");
|
||||
rssContent.AppendLine(" <ttl>60</ttl>");
|
||||
|
||||
foreach (var post in Model.Posts)
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
var postUrl = $"https://www.taxbaik.com/blog/{Uri.EscapeDataString(post.Slug)}";
|
||||
var fullDescription = post.Content ?? string.Empty;
|
||||
var itemUrl = item.Link;
|
||||
var fullDescription = item.Description ?? string.Empty;
|
||||
var safeDescription = fullDescription.Replace("]]>", "]]]]><![CDATA[>");
|
||||
|
||||
rssContent.AppendLine(" <item>");
|
||||
rssContent.AppendLine($" <title>{System.Net.WebUtility.HtmlEncode(post.Title)}</title>");
|
||||
rssContent.AppendLine($" <link>{postUrl}</link>");
|
||||
rssContent.AppendLine($" <guid isPermaLink=\"true\">{postUrl}</guid>");
|
||||
rssContent.AppendLine($" <title>{System.Net.WebUtility.HtmlEncode(item.Title)}</title>");
|
||||
rssContent.AppendLine($" <link>{itemUrl}</link>");
|
||||
rssContent.AppendLine($" <guid isPermaLink=\"true\">{item.Guid}</guid>");
|
||||
|
||||
if (post.PublishedAt is not null)
|
||||
if (item.PublishedAt is not null)
|
||||
{
|
||||
rssContent.AppendLine($" <pubDate>{post.PublishedAt.Value.ToUniversalTime():R}</pubDate>");
|
||||
rssContent.AppendLine($" <pubDate>{item.PublishedAt.Value.ToUniversalTime():R}</pubDate>");
|
||||
}
|
||||
|
||||
rssContent.AppendLine($" <description><![CDATA[{safeDescription}]]></description>");
|
||||
|
||||
@@ -7,30 +7,83 @@ namespace TaxBaik.Web.Pages;
|
||||
public class RssModel : PageModel
|
||||
{
|
||||
private readonly BlogService _blogService;
|
||||
private readonly FaqService _faqService;
|
||||
private readonly AnnouncementService _announcementService;
|
||||
|
||||
public List<BlogPost> Posts { get; set; } = [];
|
||||
public List<RssItem> Items { get; set; } = [];
|
||||
public string LastBuildDate { get; set; } = DateTime.UtcNow.ToString("R");
|
||||
|
||||
public RssModel(BlogService blogService)
|
||||
public RssModel(
|
||||
BlogService blogService,
|
||||
FaqService faqService,
|
||||
AnnouncementService announcementService)
|
||||
{
|
||||
_blogService = blogService;
|
||||
_faqService = faqService;
|
||||
_announcementService = announcementService;
|
||||
}
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 최근 50개 블로그 포스트 (RSS는 일반적으로 최신 기사만 포함)
|
||||
var (posts, _) = await _blogService.GetPublishedPagedAsync(1, 50, categoryId: null, ct: default);
|
||||
Posts = posts.OrderByDescending(p => p.PublishedAt).ToList();
|
||||
var blogTask = _blogService.GetPublishedPagedAsync(1, 50, categoryId: null, ct: default);
|
||||
var faqTask = _faqService.GetActiveAsync(HttpContext.RequestAborted);
|
||||
var announcementTask = _announcementService.GetActiveAsync(HttpContext.RequestAborted);
|
||||
|
||||
await Task.WhenAll(blogTask, faqTask, announcementTask);
|
||||
|
||||
var blogPosts = (await blogTask).Item1
|
||||
.OrderByDescending(p => p.PublishedAt)
|
||||
.Select(post => new RssItem(
|
||||
Title: post.Title,
|
||||
Link: $"https://www.taxbaik.com/blog/{Uri.EscapeDataString(post.Slug)}",
|
||||
Description: post.Content ?? string.Empty,
|
||||
PublishedAt: post.PublishedAt,
|
||||
Guid: $"https://www.taxbaik.com/blog/{Uri.EscapeDataString(post.Slug)}"))
|
||||
.ToList();
|
||||
|
||||
var faqs = (await faqTask)
|
||||
.OrderByDescending(f => f.UpdatedAt)
|
||||
.Select(faq => new RssItem(
|
||||
Title: $"FAQ: {faq.Question}",
|
||||
Link: "https://www.taxbaik.com/#faq",
|
||||
Description: faq.Answer,
|
||||
PublishedAt: faq.UpdatedAt,
|
||||
Guid: $"https://www.taxbaik.com/rss/faq/{faq.Id}"))
|
||||
.ToList();
|
||||
|
||||
var announcements = (await announcementTask)
|
||||
.OrderByDescending(a => a.UpdatedAt)
|
||||
.Select(announcement => new RssItem(
|
||||
Title: $"공지: {announcement.Title}",
|
||||
Link: "https://www.taxbaik.com/#top",
|
||||
Description: announcement.Content ?? announcement.Title,
|
||||
PublishedAt: announcement.UpdatedAt,
|
||||
Guid: $"https://www.taxbaik.com/rss/announcement/{announcement.Id}"))
|
||||
.ToList();
|
||||
|
||||
Items = blogPosts
|
||||
.Concat(faqs)
|
||||
.Concat(announcements)
|
||||
.OrderByDescending(item => item.PublishedAt ?? DateTime.MinValue)
|
||||
.Take(50)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// DB 연결 실패: 빈 포스트 목록 반환 (정적 피드 구조는 유지)
|
||||
Posts = [];
|
||||
// DB 연결 실패: 빈 피드가 되지 않도록 로컬 서버 시간 기준 단일 항목을 유지하지는 않는다.
|
||||
Items = [];
|
||||
System.Diagnostics.Debug.WriteLine($"RSS Feed: Blog posts load failed: {ex.Message}");
|
||||
}
|
||||
|
||||
LastBuildDate = Posts.FirstOrDefault()?.PublishedAt?.ToString("R") ?? DateTime.UtcNow.ToString("R");
|
||||
LastBuildDate = Items.FirstOrDefault()?.PublishedAt?.ToString("R") ?? DateTime.UtcNow.ToString("R");
|
||||
}
|
||||
|
||||
public sealed record RssItem(
|
||||
string Title,
|
||||
string Link,
|
||||
string Description,
|
||||
DateTime? PublishedAt,
|
||||
string Guid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user