using System.Diagnostics.CodeAnalysis; using Humanizer; using Markdig; using Microsoft.EntityFrameworkCore; using OliverBooth.Data; using OliverBooth.Data.Blog; namespace OliverBooth.Services; public sealed class BlogService { private readonly IDbContextFactory _dbContextFactory; private readonly MarkdownPipeline _markdownPipeline; /// /// Initializes a new instance of the class. /// /// The . /// The . public BlogService(IDbContextFactory dbContextFactory, MarkdownPipeline markdownPipeline) { _dbContextFactory = dbContextFactory; _markdownPipeline = markdownPipeline; } /// /// Gets a read-only view of all blog posts. /// /// A read-only view of all blog posts. public IReadOnlyCollection AllPosts { get { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.BlogPosts.OrderByDescending(p => p.Published).ToArray(); } } /// /// Gets the processed content of a blog post. /// /// The blog post. /// The processed content of the blog post. public string GetContent(BlogPost post) { return RenderContent(post.Body); } /// /// Gets the processed excerpt of a blog post. /// /// The blog post. /// /// When this method returns, contains if the content was trimmed; otherwise, /// . /// /// The processed excerpt of the blog post. public string GetExcerpt(BlogPost post, out bool trimmed) { ReadOnlySpan span = post.Body.AsSpan(); int moreIndex = span.IndexOf("", StringComparison.Ordinal); trimmed = moreIndex != -1 || span.Length > 256; string result = moreIndex != -1 ? span[..moreIndex].Trim().ToString() : post.Body.Truncate(256); return RenderContent(result); } /// /// Attempts to find the author of a blog post. /// /// The blog post. /// /// When this method returns, contains the associated with the specified blog post, if the /// author is found; otherwise, . /// if the author is found; otherwise, . /// is . public bool TryGetAuthor(BlogPost post, [NotNullWhen(true)] out Author? author) { if (post is null) throw new ArgumentNullException(nameof(post)); using BlogContext context = _dbContextFactory.CreateDbContext(); author = context.Authors.FirstOrDefault(a => a.Id == post.AuthorId); return author is not null; } /// /// Attempts to find a blog post by its publication date and slug. /// /// The year the post was published. /// The month the post was published. /// The day the post was published. /// The slug of the post. /// /// When this method returns, contains the associated with the specified publication /// date and slug, if the post is found; otherwise, . /// /// if the post is found; otherwise, . /// is . public bool TryGetBlogPost(int year, int month, int day, string slug, [NotNullWhen(true)] out BlogPost? post) { if (slug is null) throw new ArgumentNullException(nameof(slug)); using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.FirstOrDefault(p => p.Published.Year == year && p.Published.Month == month && p.Published.Day == day && p.Slug == slug); return post is not null; } /// /// Attempts to find a blog post by new ID. /// /// The new ID of the post. /// /// When this method returns, contains the associated with ID, if the post is found; /// otherwise, . /// /// if the post is found; otherwise, . public bool TryGetBlogPost(int postId, [NotNullWhen(true)] out BlogPost? post) { using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.FirstOrDefault(p => p.Id == postId); return post is not null; } /// /// Attempts to find a blog post by its legacy WordPress ID. /// /// The WordPress ID of the post. /// /// When this method returns, contains the associated with ID, if the post is found; /// otherwise, . /// /// if the post is found; otherwise, . public bool TryGetWordPressBlogPost(int postId, [NotNullWhen(true)] out BlogPost? post) { using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.FirstOrDefault(p => p.WordPressId == postId); return post is not null; } private string RenderContent(string content) { content = content.Replace("", string.Empty); while (content.Contains("\n\n")) { content = content.Replace("\n\n", "\n"); } return Markdig.Markdown.ToHtml(content.Trim(), _markdownPipeline); } }