diff --git a/scripts/validate_seo.py b/scripts/validate_seo.py
index 08eb54e..6f58d1c 100644
--- a/scripts/validate_seo.py
+++ b/scripts/validate_seo.py
@@ -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")
diff --git a/src/TaxBaik.Web/Pages/Rss.cshtml b/src/TaxBaik.Web/Pages/Rss.cshtml
index 2fb1cc3..0c7c59e 100644
--- a/src/TaxBaik.Web/Pages/Rss.cshtml
+++ b/src/TaxBaik.Web/Pages/Rss.cshtml
@@ -14,20 +14,20 @@
rssContent.AppendLine($" {Model.LastBuildDate}");
rssContent.AppendLine(" 60");
- 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("]]>", "]]]]>");
rssContent.AppendLine(" - ");
- rssContent.AppendLine($" {System.Net.WebUtility.HtmlEncode(post.Title)}");
- rssContent.AppendLine($" {postUrl}");
- rssContent.AppendLine($" {postUrl}");
+ rssContent.AppendLine($" {System.Net.WebUtility.HtmlEncode(item.Title)}");
+ rssContent.AppendLine($" {itemUrl}");
+ rssContent.AppendLine($" {item.Guid}");
- if (post.PublishedAt is not null)
+ if (item.PublishedAt is not null)
{
- rssContent.AppendLine($" {post.PublishedAt.Value.ToUniversalTime():R}");
+ rssContent.AppendLine($" {item.PublishedAt.Value.ToUniversalTime():R}");
}
rssContent.AppendLine($" ");
diff --git a/src/TaxBaik.Web/Pages/Rss.cshtml.cs b/src/TaxBaik.Web/Pages/Rss.cshtml.cs
index 2acdbd3..4d21c2a 100644
--- a/src/TaxBaik.Web/Pages/Rss.cshtml.cs
+++ b/src/TaxBaik.Web/Pages/Rss.cshtml.cs
@@ -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 Posts { get; set; } = [];
+ public List 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);
}