using System.Text; using System.Xml; using System.Xml.Linq; using Microsoft.Extensions.Options; namespace FrontOffice.Main.Utilities.Seo; public class SitemapGenerator { private readonly BlogPostService _blogPostService; private readonly SeoSettings _settings; private static readonly string[] StaticPaths = [ RouteConstants.Main.MainPage, RouteConstants.About.Index, RouteConstants.FAQ.Index, RouteConstants.Contact.Index, RouteConstants.Licenses.Index, RouteConstants.Blog.Index, RouteConstants.Store.Products, RouteConstants.Package.List, RouteConstants.Club.Membership, RouteConstants.Club.Features, RouteConstants.DiscountStore.Products, RouteConstants.Registration.Wizard, ]; public SitemapGenerator(BlogPostService blogPostService, IOptions settings) { _blogPostService = blogPostService; _settings = settings.Value; } public async Task GenerateAsync(CancellationToken cancellationToken = default) { var baseUrl = _settings.SiteUrl.TrimEnd('/'); var ns = XNamespace.Get("http://www.sitemaps.org/schemas/sitemap/0.9"); var urls = new List(); foreach (var path in StaticPaths) { urls.Add(CreateUrlElement(ns, $"{baseUrl}{path}", priority: path == "/" ? "1.0" : "0.8")); } var page = 1; const int pageSize = 100; while (true) { var result = await _blogPostService.GetPublishedPostsAsync(page: page, pageSize: pageSize); if (result.Posts.Count == 0) break; foreach (var post in result.Posts) { if (string.IsNullOrWhiteSpace(post.Slug)) continue; var lastMod = post.PublishedAt?.ToString("yyyy-MM-dd") ?? DateTime.UtcNow.ToString("yyyy-MM-dd"); urls.Add(CreateUrlElement( ns, $"{baseUrl}{RouteConstants.Blog.Post}{post.Slug}", lastMod: lastMod, priority: "0.6")); } if (page >= result.TotalPages) break; page++; } var document = new XDocument( new XDeclaration("1.0", "utf-8", null), new XElement(ns + "urlset", urls)); var settings = new XmlWriterSettings { Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), Indent = true, OmitXmlDeclaration = false }; using var stream = new MemoryStream(); using (var writer = XmlWriter.Create(stream, settings)) { document.Save(writer); } return Encoding.UTF8.GetString(stream.ToArray()); } public async Task GenerateBytesAsync(CancellationToken cancellationToken = default) { var xml = await GenerateAsync(cancellationToken); return Encoding.UTF8.GetBytes(xml); } private static XElement CreateUrlElement( XNamespace ns, string loc, string? lastMod = null, string changefreq = "weekly", string priority = "0.5") { var element = new XElement(ns + "url", new XElement(ns + "loc", loc), new XElement(ns + "changefreq", changefreq), new XElement(ns + "priority", priority)); if (!string.IsNullOrWhiteSpace(lastMod)) element.Add(new XElement(ns + "lastmod", lastMod)); return element; } }