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
|
|
|
}
|