using System.Diagnostics.CodeAnalysis; using Humanizer; using Markdig; using Microsoft.EntityFrameworkCore; using OliverBooth.Common.Data; using OliverBooth.Common.Data.Blog; using OliverBooth.Common.Services; using OliverBooth.Data.Blog; namespace OliverBooth.Services; /// /// Represents an implementation of . /// internal sealed class BlogPostService : IBlogPostService { private readonly IDbContextFactory _dbContextFactory; private readonly IBlogUserService _blogUserService; private readonly MarkdownPipeline _markdownPipeline; /// /// Initializes a new instance of the class. /// /// /// The used to create a . /// /// The . /// The . public BlogPostService(IDbContextFactory dbContextFactory, IBlogUserService blogUserService, MarkdownPipeline markdownPipeline) { _dbContextFactory = dbContextFactory; _blogUserService = blogUserService; _markdownPipeline = markdownPipeline; } /// public int GetBlogPostCount(Visibility visibility = Visibility.None) { using BlogContext context = _dbContextFactory.CreateDbContext(); return visibility == Visibility.None ? context.BlogPosts.Count(p => !p.IsRedirect) : context.BlogPosts.Count(p => !p.IsRedirect && p.Visibility == visibility); } /// public IReadOnlyList GetAllBlogPosts(int limit = -1) { using BlogContext context = _dbContextFactory.CreateDbContext(); IQueryable ordered = context.BlogPosts .Where(p => p.Visibility == Visibility.Published && !p.IsRedirect) .OrderByDescending(post => post.Published); if (limit > -1) { ordered = ordered.Take(limit); } return ordered.AsEnumerable().Select(CacheAuthor).ToArray(); } /// public IReadOnlyList GetBlogPosts(int page, int pageSize = 10) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.BlogPosts .Where(p => p.Visibility == Visibility.Published && !p.IsRedirect) .OrderByDescending(post => post.Published) .Skip(page * pageSize) .Take(pageSize) .ToArray().Select(CacheAuthor).ToArray(); } /// public int GetLegacyCommentCount(IBlogPost post) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.LegacyComments.Count(c => c.PostId == post.Id); } /// public IReadOnlyList GetLegacyComments(IBlogPost post) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.LegacyComments.Where(c => c.PostId == post.Id && c.ParentComment == null).ToArray(); } /// public IReadOnlyList GetLegacyReplies(ILegacyComment comment) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.LegacyComments.Where(c => c.ParentComment == comment.Id).ToArray(); } /// public IBlogPost? GetNextPost(IBlogPost blogPost) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.BlogPosts .Where(p => p.Visibility == Visibility.Published && !p.IsRedirect) .OrderBy(post => post.Published) .FirstOrDefault(post => post.Published > blogPost.Published); } /// public int GetPageCount(int pageSize = 10, Visibility visibility = Visibility.None) { float postCount = GetBlogPostCount(visibility); return (int)MathF.Ceiling(postCount / pageSize); } /// public IBlogPost? GetPreviousPost(IBlogPost blogPost) { using BlogContext context = _dbContextFactory.CreateDbContext(); return context.BlogPosts .Where(p => p.Visibility == Visibility.Published && !p.IsRedirect) .OrderByDescending(post => post.Published) .FirstOrDefault(post => post.Published < blogPost.Published); } /// public string RenderExcerpt(IBlogPost post, out bool wasTrimmed) { if (!string.IsNullOrWhiteSpace(post.Excerpt)) { wasTrimmed = false; return Markdig.Markdown.ToHtml(post.Excerpt, _markdownPipeline); } string body = post.Body; int moreIndex = body.IndexOf("", StringComparison.Ordinal); if (moreIndex == -1) { string excerpt = body.Truncate(255, "..."); wasTrimmed = body.Length > 255; return Markdig.Markdown.ToHtml(excerpt, _markdownPipeline); } wasTrimmed = true; return Markdig.Markdown.ToHtml(body[..moreIndex], _markdownPipeline); } /// public string RenderPost(IBlogPost post) { return Markdig.Markdown.ToHtml(post.Body, _markdownPipeline); } /// public bool TryGetPost(Guid id, [NotNullWhen(true)] out IBlogPost? post) { using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.Find(id); if (post is null) { return false; } CacheAuthor((BlogPost)post); return true; } /// public bool TryGetPost(int id, [NotNullWhen(true)] out IBlogPost? post) { using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.FirstOrDefault(p => p.WordPressId == id); if (post is null) { return false; } CacheAuthor((BlogPost)post); return true; } /// public bool TryGetPost(DateOnly publishDate, string slug, [NotNullWhen(true)] out IBlogPost? post) { using BlogContext context = _dbContextFactory.CreateDbContext(); post = context.BlogPosts.FirstOrDefault(post => post.Published.Year == publishDate.Year && post.Published.Month == publishDate.Month && post.Published.Day == publishDate.Day && post.Slug == slug); if (post is null) { return false; } CacheAuthor((BlogPost)post); return true; } private BlogPost CacheAuthor(BlogPost post) { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (post.Author is not null) { return post; } if (_blogUserService.TryGetUser(post.AuthorId, out IUser? user) && user is IBlogAuthor author) { post.Author = author; } return post; } }