Files
blog.stevanfreeborn.com/src/Blog/Posts/FilePostService.cs
T

149 lines
3.6 KiB
C#
Raw Normal View History

2024-04-10 01:28:38 -05:00
namespace Blog.Posts;
class FilePostService(
IOptions<FilePostServiceOptions> options,
IFileSystem fileSystem,
ILogger<FilePostService> logger
) : IPostService
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
};
private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
2024-04-12 19:33:56 -05:00
2024-04-10 01:28:38 -05:00
private const string MetaFence = "meta";
private const string IndexFile = "index.md";
private readonly FilePostServiceOptions _options = options.Value;
private readonly IFileSystem _fileSystem = fileSystem;
private readonly ILogger<FilePostService> _logger = logger;
private static (string? metadata, MarkdownDocument document) ParsePost(string postText)
{
var markDoc = Markdown.Parse(postText, MarkdownPipeline);
var postMetadata = markDoc
.Where(
x =>
x is FencedCodeBlock fencedCodeBlock &&
fencedCodeBlock.Arguments is not null &&
fencedCodeBlock.Arguments.Contains(MetaFence)
)
.Select(x => x as FencedCodeBlock)
.FirstOrDefault();
if (postMetadata is not null)
{
markDoc.Remove(postMetadata);
}
var metaContent = postMetadata?.Lines.ToString();
return (metaContent, markDoc);
}
public async Task<List<Post>> GetPostsAsync()
{
var posts = new List<Post>();
if (_fileSystem.Directory.Exists(_options.PostsDirectory) is false)
{
return posts;
}
var subDirectories = _fileSystem.Directory.GetDirectories(_options.PostsDirectory);
if (subDirectories.Length is 0)
{
return posts;
}
foreach (var subDirectory in subDirectories)
{
try
{
var indexFilePath = _fileSystem.Path.Combine(subDirectory, IndexFile);
2024-04-12 19:33:56 -05:00
2024-04-10 01:28:38 -05:00
if (_fileSystem.File.Exists(indexFilePath) is false)
{
continue;
}
2024-04-12 19:33:56 -05:00
2024-04-10 01:28:38 -05:00
var postText = await _fileSystem.File.ReadAllTextAsync(indexFilePath);
var (metaContent, document) = ParsePost(postText);
if (metaContent is null || document.Count is 0)
{
continue;
}
var post = JsonSerializer.Deserialize<Post>(metaContent, JsonSerializerOptions);
if (post is null || post.IsPublished is false)
{
continue;
}
var finalPost = post with
{
Slug = _fileSystem.Path.GetFileName(subDirectory),
};
posts.Add(finalPost);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while processing post in {SubDirectory}", subDirectory);
}
}
return [.. posts.OrderByDescending(x => x.PublishedAt)];
}
2024-04-11 00:58:22 -05:00
public async Task<PostWithContent?> GetPostAsync(string slug)
{
2024-04-12 19:33:56 -05:00
try
2024-04-11 00:58:22 -05:00
{
var postPath = _fileSystem.Path.Combine(_options.PostsDirectory, slug, IndexFile);
if (_fileSystem.File.Exists(postPath) is false)
{
return null;
}
var postText = await _fileSystem.File.ReadAllTextAsync(postPath);
var (metaContent, document) = ParsePost(postText);
if (metaContent is null || document.Count is 0)
{
return null;
}
var post = JsonSerializer.Deserialize<PostWithContent>(metaContent, JsonSerializerOptions);
if (post is null || post.IsPublished is false)
{
return null;
}
2024-04-12 19:33:56 -05:00
var postWithContent = post with
{
2024-04-11 00:58:22 -05:00
Slug = slug,
2024-04-12 19:33:56 -05:00
Content = document.ToHtml(MarkdownPipeline)
2024-04-11 00:58:22 -05:00
};
return postWithContent;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting post {Slug}", slug);
return null;
}
}
2024-04-10 01:28:38 -05:00
}