", string.Empty);
+
+ while (content.Contains("\n\n"))
+ {
+ content = content.Replace("\n\n", "\n");
+ }
+
+ return Markdig.Markdown.ToHtml(content.Trim());
+ }
+
+ public void OnGet(int year, int month, string slug)
+ {
+ using BlogContext context = _dbContextFactory.CreateDbContext();
+ Post = context.BlogPosts.FirstOrDefault(p => p.Published.Year == year &&
+ p.Published.Month == month &&
+ p.Slug == slug);
+
+ if (Post is null)
+ {
+ Response.StatusCode = 404;
+ }
+ else
+ {
+ Author = context.Authors.FirstOrDefault(a => a.Id == Post.AuthorId);
+ }
+ }
+}
diff --git a/OliverBooth/Pages/Blog/Index.cshtml b/OliverBooth/Pages/Blog/Index.cshtml
new file mode 100644
index 0000000..b17bfed
--- /dev/null
+++ b/OliverBooth/Pages/Blog/Index.cshtml
@@ -0,0 +1,16 @@
+@page
+@using OliverBooth.Data.Blog
+@model OliverBooth.Pages.Blog.Index
+
+@foreach (BlogPost post in Model.BlogPosts)
+{
+
+ @post.Published.ToString("MMMM dd, yyyy") • @Model.GetAuthor(post)?.Name
+ @Html.Raw(Model.SanitizeContent(Model.TrimContent(post.Body, out bool trimmed)))
+ if (trimmed)
+ {
+ Read more...
+ }
+}
\ No newline at end of file
diff --git a/OliverBooth/Pages/Blog/Index.cshtml.cs b/OliverBooth/Pages/Blog/Index.cshtml.cs
new file mode 100644
index 0000000..58c6d83
--- /dev/null
+++ b/OliverBooth/Pages/Blog/Index.cshtml.cs
@@ -0,0 +1,51 @@
+using Humanizer;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using OliverBooth.Data;
+using OliverBooth.Data.Blog;
+
+namespace OliverBooth.Pages.Blog;
+
+public class Index : PageModel
+{
+ private readonly IDbContextFactory _dbContextFactory;
+
+ public Index(IDbContextFactory dbContextFactory)
+ {
+ _dbContextFactory = dbContextFactory;
+ }
+
+ public IReadOnlyCollection BlogPosts { get; private set; } = ArraySegment.Empty;
+
+ public string SanitizeContent(string content)
+ {
+ content = content.Replace("", string.Empty);
+
+ while (content.Contains("\n\n"))
+ {
+ content = content.Replace("\n\n", "\n");
+ }
+
+ return Markdig.Markdown.ToHtml(content.Trim());
+ }
+
+ public string TrimContent(string content, out bool trimmed)
+ {
+ ReadOnlySpan span = content.AsSpan();
+ int moreIndex = span.IndexOf("", StringComparison.Ordinal);
+ trimmed = moreIndex != -1 || span.Length > 256;
+ return moreIndex != -1 ? span[..moreIndex].Trim().ToString() : content.Truncate(256);
+ }
+
+ public Author? GetAuthor(BlogPost post)
+ {
+ using BlogContext context = _dbContextFactory.CreateDbContext();
+ return context.Authors.FirstOrDefault(a => a.Id == post.AuthorId);
+ }
+
+ public void OnGet()
+ {
+ using BlogContext context = _dbContextFactory.CreateDbContext();
+ BlogPosts = context.BlogPosts.ToArray();
+ }
+}
diff --git a/OliverBooth/Program.cs b/OliverBooth/Program.cs
index b120cc1..2e3ec7d 100644
--- a/OliverBooth/Program.cs
+++ b/OliverBooth/Program.cs
@@ -1,13 +1,17 @@
using NLog.Extensions.Logging;
+using OliverBooth.Data;
using OliverBooth.Services;
using X10D.Hosting.DependencyInjection;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+builder.Configuration.AddTomlFile("data/config.toml", true, true);
builder.Logging.ClearProviders();
builder.Logging.AddNLog();
builder.Services.AddHostedSingleton();
+builder.Services.AddDbContextFactory();
+builder.Services.AddDbContextFactory();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllersWithViews();
builder.Services.AddRouting(options => options.LowercaseUrls = true);