From 0a9c2e82d5727eec125481992db68f3dd0f64a26 Mon Sep 17 00:00:00 2001
From: Oliver Booth
Date: Sun, 13 Aug 2023 17:33:54 +0100
Subject: [PATCH] refactor: combine sites into one
CORS was "cors"ing some issues (heh).
But also it is easier to maintain this way. Development was made much more difficult when I separated it. Combining it all also improves SEO
---
Gulpfile.js | 2 +-
OliverBooth.Blog/Dockerfile | 20 -----
OliverBooth.Blog/OliverBooth.Blog.csproj | 13 ---
OliverBooth.Blog/Pages/_ViewImports.cshtml | 2 -
OliverBooth.Blog/Pages/_ViewStart.cshtml | 3 -
OliverBooth.Blog/Program.cs | 39 --------
OliverBooth.Blog/Services/TemplateService.cs | 70 ---------------
.../Extensions/ServiceCollectionExtensions.cs | 28 ------
OliverBooth.Common/OliverBooth.Common.csproj | 39 --------
.../OliverBoothConfigureOptions.cs | 41 ---------
OliverBooth.Common/Pages/_ViewImports.cshtml | 2 -
OliverBooth.sln | 12 ---
.../.gitignore | 0
.../Controllers/Blog/BlogApiController.cs | 89 +++++++++++++++++++
.../Data/Blog}/BlogContext.cs | 4 +-
.../Data/Blog}/BlogPost.cs | 2 +-
.../Configuration/BlogPostConfiguration.cs | 2 +-
.../Configuration/TemplateConfiguration.cs | 2 +-
.../Blog}/Configuration/UserConfiguration.cs | 2 +-
.../Data/Blog}/IBlogAuthor.cs | 2 +-
.../Data/Blog}/IBlogPost.cs | 2 +-
.../Data => OliverBooth/Data/Blog}/IUser.cs | 2 +-
.../Data/Blog}/Rss/AtomLink.cs | 2 +-
.../Data/Blog}/Rss/BlogChannel.cs | 2 +-
.../Data/Blog}/Rss/BlogItem.cs | 2 +-
.../Data/Blog}/Rss/BlogRoot.cs | 2 +-
.../Data/Blog}/Template.cs | 4 +-
.../Data => OliverBooth/Data/Blog}/User.cs | 2 +-
.../Data/ITemplate.cs | 2 +-
OliverBooth/Data/Web/Template.cs | 2 -
OliverBooth/Data/{ => Web}/WebContext.cs | 3 +-
.../Extensions/WebHostBuilderExtensions.cs | 40 +++++----
.../Formatting/DateFormatter.cs | 2 +-
.../Formatting/MarkdownFormatter.cs | 3 +-
.../Markdown/Template}/TemplateExtension.cs | 4 +-
.../Markdown/Template}/TemplateInline.cs | 2 +-
.../Template}/TemplateInlineParser.cs | 2 +-
.../Markdown/Template}/TemplateRenderer.cs | 4 +-
.../Middleware/RssEndpointExtensions.cs | 2 +-
.../Middleware/RssMiddleware.cs | 8 +-
OliverBooth/OliverBooth.csproj | 17 +++-
.../Pages/Blog}/Article.cshtml | 7 +-
.../Pages/Blog}/Article.cshtml.cs | 6 +-
.../Pages/Blog}/Index.cshtml | 0
.../Pages/Blog}/Index.cshtml.cs | 6 +-
.../Pages/Blog}/RawArticle.cshtml | 0
.../Pages/Blog}/RawArticle.cshtml.cs | 6 +-
.../Pages/Shared/_Layout.cshtml | 0
.../Pages/Shared/_LoadingSpinner.cshtml | 0
OliverBooth/Program.cs | 31 +++++--
.../Services/BlogPostService.cs | 60 +++++++++----
.../Services/BlogUserService.cs | 12 +--
.../Services/IBlogPostService.cs | 10 ++-
.../Services/IBlogUserService.cs | 6 +-
.../Services/ITemplateService.cs | 4 +-
OliverBooth/Services/TemplateService.cs | 16 ++--
docker-compose.yml | 20 +----
src/ts/API.ts | 2 +-
src/ts/UI.ts | 4 +
src/ts/app.ts | 9 ++
60 files changed, 278 insertions(+), 404 deletions(-)
delete mode 100644 OliverBooth.Blog/Dockerfile
delete mode 100644 OliverBooth.Blog/OliverBooth.Blog.csproj
delete mode 100644 OliverBooth.Blog/Pages/_ViewImports.cshtml
delete mode 100644 OliverBooth.Blog/Pages/_ViewStart.cshtml
delete mode 100644 OliverBooth.Blog/Program.cs
delete mode 100644 OliverBooth.Blog/Services/TemplateService.cs
delete mode 100644 OliverBooth.Common/Extensions/ServiceCollectionExtensions.cs
delete mode 100644 OliverBooth.Common/OliverBooth.Common.csproj
delete mode 100644 OliverBooth.Common/OliverBoothConfigureOptions.cs
delete mode 100644 OliverBooth.Common/Pages/_ViewImports.cshtml
rename {OliverBooth.Common => OliverBooth}/.gitignore (100%)
create mode 100644 OliverBooth/Controllers/Blog/BlogApiController.cs
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/BlogContext.cs (96%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/BlogPost.cs (98%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Configuration/BlogPostConfiguration.cs (96%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Configuration/TemplateConfiguration.cs (92%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Configuration/UserConfiguration.cs (94%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/IBlogAuthor.cs (96%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/IBlogPost.cs (98%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/IUser.cs (98%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Rss/AtomLink.cs (89%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Rss/BlogChannel.cs (96%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Rss/BlogItem.cs (94%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Rss/BlogRoot.cs (87%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/Template.cs (97%)
rename {OliverBooth.Blog/Data => OliverBooth/Data/Blog}/User.cs (98%)
rename {OliverBooth.Common => OliverBooth}/Data/ITemplate.cs (91%)
rename OliverBooth/Data/{ => Web}/WebContext.cs (96%)
rename {OliverBooth.Common => OliverBooth}/Extensions/WebHostBuilderExtensions.cs (53%)
rename {OliverBooth.Common => OliverBooth}/Formatting/DateFormatter.cs (95%)
rename {OliverBooth.Common => OliverBooth}/Formatting/MarkdownFormatter.cs (92%)
rename {OliverBooth.Common/Markdown => OliverBooth/Markdown/Template}/TemplateExtension.cs (93%)
rename {OliverBooth.Common/Markdown => OliverBooth/Markdown/Template}/TemplateInline.cs (95%)
rename {OliverBooth.Common/Markdown => OliverBooth/Markdown/Template}/TemplateInlineParser.cs (99%)
rename {OliverBooth.Common/Markdown => OliverBooth/Markdown/Template}/TemplateRenderer.cs (91%)
rename {OliverBooth.Blog => OliverBooth}/Middleware/RssEndpointExtensions.cs (90%)
rename {OliverBooth.Blog => OliverBooth}/Middleware/RssMiddleware.cs (95%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/Article.cshtml (94%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/Article.cshtml.cs (94%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/Index.cshtml (100%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/Index.cshtml.cs (93%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/RawArticle.cshtml (100%)
rename {OliverBooth.Blog/Pages => OliverBooth/Pages/Blog}/RawArticle.cshtml.cs (93%)
rename {OliverBooth.Common => OliverBooth}/Pages/Shared/_Layout.cshtml (100%)
rename {OliverBooth.Common => OliverBooth}/Pages/Shared/_LoadingSpinner.cshtml (100%)
rename {OliverBooth.Blog => OliverBooth}/Services/BlogPostService.cs (65%)
rename OliverBooth.Blog/Services/UserService.cs => OliverBooth/Services/BlogUserService.cs (66%)
rename {OliverBooth.Blog => OliverBooth}/Services/IBlogPostService.cs (94%)
rename OliverBooth.Blog/Services/IUserService.cs => OliverBooth/Services/IBlogUserService.cs (87%)
rename {OliverBooth.Common => OliverBooth}/Services/ITemplateService.cs (88%)
diff --git a/Gulpfile.js b/Gulpfile.js
index c8da2b6..d36925d 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -7,7 +7,7 @@ const terser = require('gulp-terser');
const webpack = require('webpack-stream');
const srcDir = 'src';
-const destDir = 'OliverBooth.Common/wwwroot';
+const destDir = 'OliverBooth/wwwroot';
function compileSCSS() {
return gulp.src(`${srcDir}/scss/**/*.scss`)
diff --git a/OliverBooth.Blog/Dockerfile b/OliverBooth.Blog/Dockerfile
deleted file mode 100644
index ae04ebe..0000000
--- a/OliverBooth.Blog/Dockerfile
+++ /dev/null
@@ -1,20 +0,0 @@
-FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
-WORKDIR /app
-EXPOSE 80
-EXPOSE 443
-
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
-WORKDIR /src
-COPY ["OliverBooth.Blog/OliverBooth.Blog.csproj", "OliverBooth.Blog/"]
-RUN dotnet restore "OliverBooth.Blog/OliverBooth.Blog.csproj"
-COPY . .
-WORKDIR "/src/OliverBooth.Blog"
-RUN dotnet build "OliverBooth.Blog.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "OliverBooth.Blog.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "OliverBooth.Blog.dll"]
diff --git a/OliverBooth.Blog/OliverBooth.Blog.csproj b/OliverBooth.Blog/OliverBooth.Blog.csproj
deleted file mode 100644
index fd902ff..0000000
--- a/OliverBooth.Blog/OliverBooth.Blog.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- net7.0
- enable
- enable
-
-
-
-
-
-
-
diff --git a/OliverBooth.Blog/Pages/_ViewImports.cshtml b/OliverBooth.Blog/Pages/_ViewImports.cshtml
deleted file mode 100644
index b7624fb..0000000
--- a/OliverBooth.Blog/Pages/_ViewImports.cshtml
+++ /dev/null
@@ -1,2 +0,0 @@
-@namespace OliverBooth.Blog.Pages
-@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
diff --git a/OliverBooth.Blog/Pages/_ViewStart.cshtml b/OliverBooth.Blog/Pages/_ViewStart.cshtml
deleted file mode 100644
index d641c67..0000000
--- a/OliverBooth.Blog/Pages/_ViewStart.cshtml
+++ /dev/null
@@ -1,3 +0,0 @@
-@{
- Layout = "_Layout";
-}
\ No newline at end of file
diff --git a/OliverBooth.Blog/Program.cs b/OliverBooth.Blog/Program.cs
deleted file mode 100644
index 8d28aea..0000000
--- a/OliverBooth.Blog/Program.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using OliverBooth.Blog.Data;
-using OliverBooth.Blog.Middleware;
-using OliverBooth.Blog.Services;
-using OliverBooth.Common;
-using OliverBooth.Common.Extensions;
-using OliverBooth.Common.Services;
-using Serilog;
-
-Log.Logger = new LoggerConfiguration()
- .WriteTo.Console()
- .WriteTo.File("logs/latest.log", rollingInterval: RollingInterval.Day)
- .CreateLogger();
-
-WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
-builder.Configuration.AddTomlFile("data/config.toml", true, true);
-builder.Logging.ClearProviders();
-builder.Logging.AddSerilog();
-
-builder.Services.AddMarkdownPipeline();
-builder.Services.ConfigureOptions();
-builder.Services.AddDbContextFactory();
-builder.Services.AddSingleton();
-builder.Services.AddSingleton();
-builder.Services.AddSingleton();
-builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
-builder.Services.AddControllersWithViews();
-
-builder.WebHost.AddCertificateFromEnvironment(2846, 5050);
-
-WebApplication app = builder.Build();
-
-app.UseHttpsRedirection();
-app.UseStaticFiles();
-app.UseRouting();
-app.MapRssFeed("/feed");
-app.MapRazorPages();
-app.MapControllers();
-
-app.Run();
diff --git a/OliverBooth.Blog/Services/TemplateService.cs b/OliverBooth.Blog/Services/TemplateService.cs
deleted file mode 100644
index 394753e..0000000
--- a/OliverBooth.Blog/Services/TemplateService.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System.Buffers.Binary;
-using Microsoft.EntityFrameworkCore;
-using OliverBooth.Blog.Data;
-using OliverBooth.Common.Formatting;
-using OliverBooth.Common.Markdown;
-using OliverBooth.Common.Services;
-using SmartFormat;
-using SmartFormat.Extensions;
-
-namespace OliverBooth.Blog.Services;
-
-///
-/// Represents a service that renders MediaWiki-style templates.
-///
-internal sealed class TemplateService : ITemplateService
-{
- private static readonly Random Random = new();
- private readonly IDbContextFactory _webContextFactory;
- private readonly SmartFormatter _formatter;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- /// The factory.
- public TemplateService(IServiceProvider serviceProvider, IDbContextFactory webContextFactory)
- {
- _formatter = Smart.CreateDefaultSmartFormat();
- _formatter.AddExtensions(new DefaultSource());
- _formatter.AddExtensions(new ReflectionSource());
- _formatter.AddExtensions(new DateFormatter());
- _formatter.AddExtensions(new MarkdownFormatter(serviceProvider));
-
- _webContextFactory = webContextFactory;
- }
-
- ///
- public string RenderTemplate(TemplateInline templateInline)
- {
- if (templateInline is null) throw new ArgumentNullException(nameof(templateInline));
-
- using BlogContext webContext = _webContextFactory.CreateDbContext();
- Template? template = webContext.Templates.Find(templateInline.Name);
- if (template is null)
- {
- return $"{{{{{templateInline.Name}}}}}";
- }
-
- Span randomBytes = stackalloc byte[20];
- Random.NextBytes(randomBytes);
-
- var formatted = new
- {
- templateInline.ArgumentList,
- templateInline.ArgumentString,
- templateInline.Params,
- RandomInt = BinaryPrimitives.ReadInt32LittleEndian(randomBytes[..4]),
- RandomGuid = new Guid(randomBytes[4..]).ToString("N"),
- };
-
- try
- {
- return _formatter.Format(template.FormatString, formatted);
- }
- catch
- {
- return $"{{{{{templateInline.Name}|{templateInline.ArgumentString}}}}}";
- }
- }
-}
diff --git a/OliverBooth.Common/Extensions/ServiceCollectionExtensions.cs b/OliverBooth.Common/Extensions/ServiceCollectionExtensions.cs
deleted file mode 100644
index d45765b..0000000
--- a/OliverBooth.Common/Extensions/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Markdig;
-using Microsoft.Extensions.DependencyInjection;
-using OliverBooth.Common.Markdown;
-using OliverBooth.Common.Services;
-
-namespace OliverBooth.Common.Extensions;
-
-///
-/// Extension methods for .
-///
-public static class ServiceCollectionExtensions
-{
- ///
- /// Adds the Markdown pipeline to the .
- ///
- /// The .
- /// The .
- public static IServiceCollection AddMarkdownPipeline(this IServiceCollection serviceCollection)
- {
- return serviceCollection.AddSingleton(provider => new MarkdownPipelineBuilder()
- .Use(new TemplateExtension(provider.GetRequiredService()))
- .UseAdvancedExtensions()
- .UseBootstrap()
- .UseEmojiAndSmiley()
- .UseSmartyPants()
- .Build());
- }
-}
diff --git a/OliverBooth.Common/OliverBooth.Common.csproj b/OliverBooth.Common/OliverBooth.Common.csproj
deleted file mode 100644
index f29bf7c..0000000
--- a/OliverBooth.Common/OliverBooth.Common.csproj
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
- net7.0
- enable
- enable
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OliverBooth.Common/OliverBoothConfigureOptions.cs b/OliverBooth.Common/OliverBoothConfigureOptions.cs
deleted file mode 100644
index 98b7b13..0000000
--- a/OliverBooth.Common/OliverBoothConfigureOptions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.StaticFiles;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Options;
-
-namespace OliverBooth.Common;
-
-///
-/// Represents the middleware to configure static file options.
-///
-public sealed class OliverBoothConfigureOptions : IPostConfigureOptions
-{
- private readonly IWebHostEnvironment _environment;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- public OliverBoothConfigureOptions(IWebHostEnvironment environment)
- {
- _environment = environment;
- }
-
- ///
- public void PostConfigure(string? name, StaticFileOptions options)
- {
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
- options.ContentTypeProvider ??= new FileExtensionContentTypeProvider();
-
- if (options.FileProvider == null && _environment.WebRootFileProvider == null)
- {
- throw new InvalidOperationException("Missing FileProvider.");
- }
-
- options.FileProvider ??= _environment.WebRootFileProvider;
-
- var filesProvider = new ManifestEmbeddedFileProvider(GetType().Assembly, "wwwroot");
- options.FileProvider = new CompositeFileProvider(options.FileProvider, filesProvider);
- }
-}
diff --git a/OliverBooth.Common/Pages/_ViewImports.cshtml b/OliverBooth.Common/Pages/_ViewImports.cshtml
deleted file mode 100644
index e0daad2..0000000
--- a/OliverBooth.Common/Pages/_ViewImports.cshtml
+++ /dev/null
@@ -1,2 +0,0 @@
-@namespace OliverBooth.Common.Pages
-@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
diff --git a/OliverBooth.sln b/OliverBooth.sln
index b61ab0e..5e51d9a 100644
--- a/OliverBooth.sln
+++ b/OliverBooth.sln
@@ -34,10 +34,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ts", "ts", "{BB9F76AC-292A-
src\ts\Input.ts = src\ts\Input.ts
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Blog", "OliverBooth.Blog\OliverBooth.Blog.csproj", "{B114A2ED-3015-43C5-B0CE-B755B18F49D0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Common", "OliverBooth.Common\OliverBooth.Common.csproj", "{38DEB2FA-3DF4-4D37-A12D-22CAEEA3A8AB}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -48,14 +44,6 @@ Global
{A58A6FA3-480C-400B-822A-3786741BF39C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A58A6FA3-480C-400B-822A-3786741BF39C}.Release|Any CPU.Build.0 = Release|Any CPU
- {B114A2ED-3015-43C5-B0CE-B755B18F49D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B114A2ED-3015-43C5-B0CE-B755B18F49D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B114A2ED-3015-43C5-B0CE-B755B18F49D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B114A2ED-3015-43C5-B0CE-B755B18F49D0}.Release|Any CPU.Build.0 = Release|Any CPU
- {38DEB2FA-3DF4-4D37-A12D-22CAEEA3A8AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {38DEB2FA-3DF4-4D37-A12D-22CAEEA3A8AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {38DEB2FA-3DF4-4D37-A12D-22CAEEA3A8AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {38DEB2FA-3DF4-4D37-A12D-22CAEEA3A8AB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{822F528E-3CA7-4B7D-9250-BD248ADA7BAE} = {8A323E64-E41E-4780-99FD-17BF58961FB5}
diff --git a/OliverBooth.Common/.gitignore b/OliverBooth/.gitignore
similarity index 100%
rename from OliverBooth.Common/.gitignore
rename to OliverBooth/.gitignore
diff --git a/OliverBooth/Controllers/Blog/BlogApiController.cs b/OliverBooth/Controllers/Blog/BlogApiController.cs
new file mode 100644
index 0000000..1d5a6a0
--- /dev/null
+++ b/OliverBooth/Controllers/Blog/BlogApiController.cs
@@ -0,0 +1,89 @@
+using Humanizer;
+using Microsoft.AspNetCore.Mvc;
+using OliverBooth.Data.Blog;
+using OliverBooth.Services;
+
+namespace OliverBooth.Controllers.Blog;
+
+///
+/// Represents a controller for the blog API.
+///
+[ApiController]
+[Route("api/blog")]
+[Produces("application/json")]
+public sealed class BlogApiController : ControllerBase
+{
+ private readonly IBlogPostService _blogPostService;
+ private readonly IBlogUserService _userService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ public BlogApiController(IBlogPostService blogPostService, IBlogUserService userService)
+ {
+ _blogPostService = blogPostService;
+ _userService = userService;
+ }
+
+ [Route("count")]
+ public IActionResult Count()
+ {
+ return Ok(new { count = _blogPostService.GetBlogPostCount() });
+ }
+
+ [HttpGet("posts/{page:int?}")]
+ public IActionResult GetAllBlogPosts(int page = 0)
+ {
+ const int itemsPerPage = 10;
+ IReadOnlyList allPosts = _blogPostService.GetBlogPosts(page, itemsPerPage);
+ return Ok(allPosts.Select(post => CreatePostObject(post)));
+ }
+
+ [HttpGet("author/{id:guid}")]
+ public IActionResult GetAuthor(Guid id)
+ {
+ if (!_userService.TryGetUser(id, out IUser? author)) return NotFound();
+
+ return Ok(new
+ {
+ id = author.Id,
+ name = author.DisplayName,
+ avatarUrl = author.AvatarUrl,
+ });
+ }
+
+ [HttpGet("post/{id:guid?}")]
+ public IActionResult GetPost(Guid id)
+ {
+ if (!_blogPostService.TryGetPost(id, out IBlogPost? post)) return NotFound();
+ return Ok(CreatePostObject(post, true));
+ }
+
+ private object CreatePostObject(IBlogPost post, bool includeContent = false)
+ {
+ return new
+ {
+ id = post.Id,
+ commentsEnabled = post.EnableComments,
+ identifier = post.GetDisqusIdentifier(),
+ author = post.Author.Id,
+ title = post.Title,
+ published = post.Published.ToUnixTimeSeconds(),
+ formattedDate = post.Published.ToString("dddd, d MMMM yyyy HH:mm"),
+ updated = post.Updated?.ToUnixTimeSeconds(),
+ humanizedTimestamp = post.Updated?.Humanize() ?? post.Published.Humanize(),
+ excerpt = _blogPostService.RenderExcerpt(post, out bool trimmed),
+ content = includeContent ? _blogPostService.RenderPost(post) : null,
+ trimmed,
+ url = new
+ {
+ year = post.Published.ToString("yyyy"),
+ month = post.Published.ToString("MM"),
+ day = post.Published.ToString("dd"),
+ slug = post.Slug
+ }
+ };
+ }
+}
diff --git a/OliverBooth.Blog/Data/BlogContext.cs b/OliverBooth/Data/Blog/BlogContext.cs
similarity index 96%
rename from OliverBooth.Blog/Data/BlogContext.cs
rename to OliverBooth/Data/Blog/BlogContext.cs
index 54512ff..ff4c0a2 100644
--- a/OliverBooth.Blog/Data/BlogContext.cs
+++ b/OliverBooth/Data/Blog/BlogContext.cs
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
-using OliverBooth.Blog.Data.Configuration;
+using OliverBooth.Data.Blog.Configuration;
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents a session with the blog database.
diff --git a/OliverBooth.Blog/Data/BlogPost.cs b/OliverBooth/Data/Blog/BlogPost.cs
similarity index 98%
rename from OliverBooth.Blog/Data/BlogPost.cs
rename to OliverBooth/Data/Blog/BlogPost.cs
index ccea0d5..eb0fe2c 100644
--- a/OliverBooth.Blog/Data/BlogPost.cs
+++ b/OliverBooth/Data/Blog/BlogPost.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations.Schema;
using SmartFormat;
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
internal sealed class BlogPost : IBlogPost
diff --git a/OliverBooth.Blog/Data/Configuration/BlogPostConfiguration.cs b/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs
similarity index 96%
rename from OliverBooth.Blog/Data/Configuration/BlogPostConfiguration.cs
rename to OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs
index 90a0bb2..1fd1b9c 100644
--- a/OliverBooth.Blog/Data/Configuration/BlogPostConfiguration.cs
+++ b/OliverBooth/Data/Blog/Configuration/BlogPostConfiguration.cs
@@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-namespace OliverBooth.Blog.Data.Configuration;
+namespace OliverBooth.Data.Blog.Configuration;
internal sealed class BlogPostConfiguration : IEntityTypeConfiguration
{
diff --git a/OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs b/OliverBooth/Data/Blog/Configuration/TemplateConfiguration.cs
similarity index 92%
rename from OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs
rename to OliverBooth/Data/Blog/Configuration/TemplateConfiguration.cs
index e72ccb4..9366eb7 100644
--- a/OliverBooth.Blog/Data/Configuration/TemplateConfiguration.cs
+++ b/OliverBooth/Data/Blog/Configuration/TemplateConfiguration.cs
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-namespace OliverBooth.Blog.Data.Configuration;
+namespace OliverBooth.Data.Blog.Configuration;
///
/// Represents the configuration for the entity.
diff --git a/OliverBooth.Blog/Data/Configuration/UserConfiguration.cs b/OliverBooth/Data/Blog/Configuration/UserConfiguration.cs
similarity index 94%
rename from OliverBooth.Blog/Data/Configuration/UserConfiguration.cs
rename to OliverBooth/Data/Blog/Configuration/UserConfiguration.cs
index f25aed1..65060d4 100644
--- a/OliverBooth.Blog/Data/Configuration/UserConfiguration.cs
+++ b/OliverBooth/Data/Blog/Configuration/UserConfiguration.cs
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-namespace OliverBooth.Blog.Data.Configuration;
+namespace OliverBooth.Data.Blog.Configuration;
internal sealed class UserConfiguration : IEntityTypeConfiguration
{
diff --git a/OliverBooth.Blog/Data/IBlogAuthor.cs b/OliverBooth/Data/Blog/IBlogAuthor.cs
similarity index 96%
rename from OliverBooth.Blog/Data/IBlogAuthor.cs
rename to OliverBooth/Data/Blog/IBlogAuthor.cs
index cf9b4c0..3f1c3e1 100644
--- a/OliverBooth.Blog/Data/IBlogAuthor.cs
+++ b/OliverBooth/Data/Blog/IBlogAuthor.cs
@@ -1,4 +1,4 @@
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents the author of a blog post.
diff --git a/OliverBooth.Blog/Data/IBlogPost.cs b/OliverBooth/Data/Blog/IBlogPost.cs
similarity index 98%
rename from OliverBooth.Blog/Data/IBlogPost.cs
rename to OliverBooth/Data/Blog/IBlogPost.cs
index af79935..177badc 100644
--- a/OliverBooth.Blog/Data/IBlogPost.cs
+++ b/OliverBooth/Data/Blog/IBlogPost.cs
@@ -1,4 +1,4 @@
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents a blog post.
diff --git a/OliverBooth.Blog/Data/IUser.cs b/OliverBooth/Data/Blog/IUser.cs
similarity index 98%
rename from OliverBooth.Blog/Data/IUser.cs
rename to OliverBooth/Data/Blog/IUser.cs
index c9999dd..912db14 100644
--- a/OliverBooth.Blog/Data/IUser.cs
+++ b/OliverBooth/Data/Blog/IUser.cs
@@ -1,4 +1,4 @@
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents a user which can log in to the blog.
diff --git a/OliverBooth.Blog/Data/Rss/AtomLink.cs b/OliverBooth/Data/Blog/Rss/AtomLink.cs
similarity index 89%
rename from OliverBooth.Blog/Data/Rss/AtomLink.cs
rename to OliverBooth/Data/Blog/Rss/AtomLink.cs
index eeda606..64ce1b9 100644
--- a/OliverBooth.Blog/Data/Rss/AtomLink.cs
+++ b/OliverBooth/Data/Blog/Rss/AtomLink.cs
@@ -1,6 +1,6 @@
using System.Xml.Serialization;
-namespace OliverBooth.Data.Rss;
+namespace OliverBooth.Data.Blog.Rss;
public sealed class AtomLink
{
diff --git a/OliverBooth.Blog/Data/Rss/BlogChannel.cs b/OliverBooth/Data/Blog/Rss/BlogChannel.cs
similarity index 96%
rename from OliverBooth.Blog/Data/Rss/BlogChannel.cs
rename to OliverBooth/Data/Blog/Rss/BlogChannel.cs
index c871890..8546a8d 100644
--- a/OliverBooth.Blog/Data/Rss/BlogChannel.cs
+++ b/OliverBooth/Data/Blog/Rss/BlogChannel.cs
@@ -1,6 +1,6 @@
using System.Xml.Serialization;
-namespace OliverBooth.Data.Rss;
+namespace OliverBooth.Data.Blog.Rss;
public sealed class BlogChannel
{
diff --git a/OliverBooth.Blog/Data/Rss/BlogItem.cs b/OliverBooth/Data/Blog/Rss/BlogItem.cs
similarity index 94%
rename from OliverBooth.Blog/Data/Rss/BlogItem.cs
rename to OliverBooth/Data/Blog/Rss/BlogItem.cs
index e4ae38d..3b22cf7 100644
--- a/OliverBooth.Blog/Data/Rss/BlogItem.cs
+++ b/OliverBooth/Data/Blog/Rss/BlogItem.cs
@@ -1,6 +1,6 @@
using System.Xml.Serialization;
-namespace OliverBooth.Data.Rss;
+namespace OliverBooth.Data.Blog.Rss;
public sealed class BlogItem
{
diff --git a/OliverBooth.Blog/Data/Rss/BlogRoot.cs b/OliverBooth/Data/Blog/Rss/BlogRoot.cs
similarity index 87%
rename from OliverBooth.Blog/Data/Rss/BlogRoot.cs
rename to OliverBooth/Data/Blog/Rss/BlogRoot.cs
index ea4a1ec..a071794 100644
--- a/OliverBooth.Blog/Data/Rss/BlogRoot.cs
+++ b/OliverBooth/Data/Blog/Rss/BlogRoot.cs
@@ -1,6 +1,6 @@
using System.Xml.Serialization;
-namespace OliverBooth.Data.Rss;
+namespace OliverBooth.Data.Blog.Rss;
[XmlRoot("rss")]
public sealed class BlogRoot
diff --git a/OliverBooth.Blog/Data/Template.cs b/OliverBooth/Data/Blog/Template.cs
similarity index 97%
rename from OliverBooth.Blog/Data/Template.cs
rename to OliverBooth/Data/Blog/Template.cs
index c20b9ce..dbb611a 100644
--- a/OliverBooth.Blog/Data/Template.cs
+++ b/OliverBooth/Data/Blog/Template.cs
@@ -1,6 +1,4 @@
-using OliverBooth.Common.Data;
-
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents a MediaWiki-style template.
diff --git a/OliverBooth.Blog/Data/User.cs b/OliverBooth/Data/Blog/User.cs
similarity index 98%
rename from OliverBooth.Blog/Data/User.cs
rename to OliverBooth/Data/Blog/User.cs
index c374982..dcd50ab 100644
--- a/OliverBooth.Blog/Data/User.cs
+++ b/OliverBooth/Data/Blog/User.cs
@@ -3,7 +3,7 @@ using System.Security.Cryptography;
using System.Text;
using Cysharp.Text;
-namespace OliverBooth.Blog.Data;
+namespace OliverBooth.Data.Blog;
///
/// Represents a user.
diff --git a/OliverBooth.Common/Data/ITemplate.cs b/OliverBooth/Data/ITemplate.cs
similarity index 91%
rename from OliverBooth.Common/Data/ITemplate.cs
rename to OliverBooth/Data/ITemplate.cs
index 9adcaa3..25c0e21 100644
--- a/OliverBooth.Common/Data/ITemplate.cs
+++ b/OliverBooth/Data/ITemplate.cs
@@ -1,4 +1,4 @@
-namespace OliverBooth.Common.Data;
+namespace OliverBooth.Data;
///
/// Represents a template.
diff --git a/OliverBooth/Data/Web/Template.cs b/OliverBooth/Data/Web/Template.cs
index 83677c6..74347e4 100644
--- a/OliverBooth/Data/Web/Template.cs
+++ b/OliverBooth/Data/Web/Template.cs
@@ -1,5 +1,3 @@
-using OliverBooth.Common.Data;
-
namespace OliverBooth.Data.Web;
///
diff --git a/OliverBooth/Data/WebContext.cs b/OliverBooth/Data/Web/WebContext.cs
similarity index 96%
rename from OliverBooth/Data/WebContext.cs
rename to OliverBooth/Data/Web/WebContext.cs
index eb52e95..dac5c31 100644
--- a/OliverBooth/Data/WebContext.cs
+++ b/OliverBooth/Data/Web/WebContext.cs
@@ -1,8 +1,7 @@
using Microsoft.EntityFrameworkCore;
-using OliverBooth.Data.Web;
using OliverBooth.Data.Web.Configuration;
-namespace OliverBooth.Data;
+namespace OliverBooth.Data.Web;
///
/// Represents a session with the web database.
diff --git a/OliverBooth.Common/Extensions/WebHostBuilderExtensions.cs b/OliverBooth/Extensions/WebHostBuilderExtensions.cs
similarity index 53%
rename from OliverBooth.Common/Extensions/WebHostBuilderExtensions.cs
rename to OliverBooth/Extensions/WebHostBuilderExtensions.cs
index ef01c3a..42a83dd 100644
--- a/OliverBooth.Common/Extensions/WebHostBuilderExtensions.cs
+++ b/OliverBooth/Extensions/WebHostBuilderExtensions.cs
@@ -1,8 +1,6 @@
-using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
-using Microsoft.AspNetCore.Hosting;
-namespace OliverBooth.Common.Extensions;
+namespace OliverBooth.Extensions;
///
/// Extension methods for .
@@ -23,31 +21,35 @@ public static class WebHostBuilderExtensions
return builder.UseKestrel(options =>
{
string certPath = Environment.GetEnvironmentVariable("SSL_CERT_PATH")!;
+ if (string.IsNullOrWhiteSpace(certPath))
+ {
+ Console.WriteLine("Certificate path not specified. Using HTTP");
+ options.ListenAnyIP(httpPort);
+ return;
+ }
+
if (!File.Exists(certPath))
{
+ Console.Error.WriteLine("Certificate not found. Using HTTP");
options.ListenAnyIP(httpPort);
return;
}
string? keyPath = Environment.GetEnvironmentVariable("SSL_KEY_PATH");
- if (string.IsNullOrWhiteSpace(keyPath) || !File.Exists(keyPath)) keyPath = null;
-
- options.ListenAnyIP(httpsPort, options =>
+ if (string.IsNullOrWhiteSpace(keyPath))
{
- X509Certificate2 cert = CreateCertFromPemFile(certPath, keyPath);
- options.UseHttps(cert);
- });
- return;
-
- static X509Certificate2 CreateCertFromPemFile(string certPath, string? keyPath)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- return X509Certificate2.CreateFromPemFile(certPath, keyPath);
-
- //workaround for windows issue https://github.com/dotnet/runtime/issues/23749#issuecomment-388231655
- using var cert = X509Certificate2.CreateFromPemFile(certPath, keyPath);
- return new X509Certificate2(cert.Export(X509ContentType.Pkcs12));
+ Console.WriteLine("Certificate found, but no key provided. Using certificate only");
+ keyPath = null;
}
+ else if (!File.Exists(keyPath))
+ {
+ Console.Error.WriteLine("Certificate found, but the provided key was not. Using certificate only");
+ keyPath = null;
+ }
+
+ Console.WriteLine($"Using HTTPS with certificate found at {certPath}:{keyPath}");
+ var certificate = X509Certificate2.CreateFromPemFile(certPath, keyPath);
+ options.ListenAnyIP(httpsPort, configure => configure.UseHttps(certificate));
});
}
}
diff --git a/OliverBooth.Common/Formatting/DateFormatter.cs b/OliverBooth/Formatting/DateFormatter.cs
similarity index 95%
rename from OliverBooth.Common/Formatting/DateFormatter.cs
rename to OliverBooth/Formatting/DateFormatter.cs
index ce4a430..8d98a3b 100644
--- a/OliverBooth.Common/Formatting/DateFormatter.cs
+++ b/OliverBooth/Formatting/DateFormatter.cs
@@ -1,7 +1,7 @@
using System.Globalization;
using SmartFormat.Core.Extensions;
-namespace OliverBooth.Common.Formatting;
+namespace OliverBooth.Formatting;
///
/// Represents a SmartFormat formatter that formats a date.
diff --git a/OliverBooth.Common/Formatting/MarkdownFormatter.cs b/OliverBooth/Formatting/MarkdownFormatter.cs
similarity index 92%
rename from OliverBooth.Common/Formatting/MarkdownFormatter.cs
rename to OliverBooth/Formatting/MarkdownFormatter.cs
index 077c68b..c682e2c 100644
--- a/OliverBooth.Common/Formatting/MarkdownFormatter.cs
+++ b/OliverBooth/Formatting/MarkdownFormatter.cs
@@ -1,8 +1,7 @@
using Markdig;
-using Microsoft.Extensions.DependencyInjection;
using SmartFormat.Core.Extensions;
-namespace OliverBooth.Common.Formatting;
+namespace OliverBooth.Formatting;
///
/// Represents a SmartFormat formatter that formats markdown.
diff --git a/OliverBooth.Common/Markdown/TemplateExtension.cs b/OliverBooth/Markdown/Template/TemplateExtension.cs
similarity index 93%
rename from OliverBooth.Common/Markdown/TemplateExtension.cs
rename to OliverBooth/Markdown/Template/TemplateExtension.cs
index ab26721..ec1ea0d 100644
--- a/OliverBooth.Common/Markdown/TemplateExtension.cs
+++ b/OliverBooth/Markdown/Template/TemplateExtension.cs
@@ -1,8 +1,8 @@
using Markdig;
using Markdig.Renderers;
-using OliverBooth.Common.Services;
+using OliverBooth.Services;
-namespace OliverBooth.Common.Markdown;
+namespace OliverBooth.Markdown.Template;
///
/// Represents a Markdown extension that adds support for MediaWiki-style templates.
diff --git a/OliverBooth.Common/Markdown/TemplateInline.cs b/OliverBooth/Markdown/Template/TemplateInline.cs
similarity index 95%
rename from OliverBooth.Common/Markdown/TemplateInline.cs
rename to OliverBooth/Markdown/Template/TemplateInline.cs
index 4c4565d..34be9ad 100644
--- a/OliverBooth.Common/Markdown/TemplateInline.cs
+++ b/OliverBooth/Markdown/Template/TemplateInline.cs
@@ -1,6 +1,6 @@
using Markdig.Syntax.Inlines;
-namespace OliverBooth.Common.Markdown;
+namespace OliverBooth.Markdown.Template;
///
/// Represents a Markdown inline element that represents a MediaWiki-style template.
diff --git a/OliverBooth.Common/Markdown/TemplateInlineParser.cs b/OliverBooth/Markdown/Template/TemplateInlineParser.cs
similarity index 99%
rename from OliverBooth.Common/Markdown/TemplateInlineParser.cs
rename to OliverBooth/Markdown/Template/TemplateInlineParser.cs
index 88731aa..4c3d7b5 100644
--- a/OliverBooth.Common/Markdown/TemplateInlineParser.cs
+++ b/OliverBooth/Markdown/Template/TemplateInlineParser.cs
@@ -2,7 +2,7 @@ using Cysharp.Text;
using Markdig.Helpers;
using Markdig.Parsers;
-namespace OliverBooth.Common.Markdown;
+namespace OliverBooth.Markdown.Template;
///
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
diff --git a/OliverBooth.Common/Markdown/TemplateRenderer.cs b/OliverBooth/Markdown/Template/TemplateRenderer.cs
similarity index 91%
rename from OliverBooth.Common/Markdown/TemplateRenderer.cs
rename to OliverBooth/Markdown/Template/TemplateRenderer.cs
index 4df9edd..fea4ab3 100644
--- a/OliverBooth.Common/Markdown/TemplateRenderer.cs
+++ b/OliverBooth/Markdown/Template/TemplateRenderer.cs
@@ -1,8 +1,8 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
-using OliverBooth.Common.Services;
+using OliverBooth.Services;
-namespace OliverBooth.Common.Markdown;
+namespace OliverBooth.Markdown.Template;
///
/// Represents a Markdown object renderer that handles elements.
diff --git a/OliverBooth.Blog/Middleware/RssEndpointExtensions.cs b/OliverBooth/Middleware/RssEndpointExtensions.cs
similarity index 90%
rename from OliverBooth.Blog/Middleware/RssEndpointExtensions.cs
rename to OliverBooth/Middleware/RssEndpointExtensions.cs
index 412657a..8b0d501 100644
--- a/OliverBooth.Blog/Middleware/RssEndpointExtensions.cs
+++ b/OliverBooth/Middleware/RssEndpointExtensions.cs
@@ -1,4 +1,4 @@
-namespace OliverBooth.Blog.Middleware;
+namespace OliverBooth.Middleware;
internal static class RssEndpointExtensions
{
diff --git a/OliverBooth.Blog/Middleware/RssMiddleware.cs b/OliverBooth/Middleware/RssMiddleware.cs
similarity index 95%
rename from OliverBooth.Blog/Middleware/RssMiddleware.cs
rename to OliverBooth/Middleware/RssMiddleware.cs
index 57a52ff..8f38ea2 100644
--- a/OliverBooth.Blog/Middleware/RssMiddleware.cs
+++ b/OliverBooth/Middleware/RssMiddleware.cs
@@ -1,10 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;
-using OliverBooth.Blog.Data;
-using OliverBooth.Blog.Services;
-using OliverBooth.Data.Rss;
+using OliverBooth.Data.Blog;
+using OliverBooth.Data.Blog.Rss;
+using OliverBooth.Services;
-namespace OliverBooth.Blog.Middleware;
+namespace OliverBooth.Middleware;
internal sealed class RssMiddleware
{
diff --git a/OliverBooth/OliverBooth.csproj b/OliverBooth/OliverBooth.csproj
index c5969e1..53d2bea 100644
--- a/OliverBooth/OliverBooth.csproj
+++ b/OliverBooth/OliverBooth.csproj
@@ -8,7 +8,22 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OliverBooth.Blog/Pages/Article.cshtml b/OliverBooth/Pages/Blog/Article.cshtml
similarity index 94%
rename from OliverBooth.Blog/Pages/Article.cshtml
rename to OliverBooth/Pages/Blog/Article.cshtml
index e1480c4..25fcc5b 100644
--- a/OliverBooth.Blog/Pages/Article.cshtml
+++ b/OliverBooth/Pages/Blog/Article.cshtml
@@ -1,6 +1,6 @@
@page "/{year:int}/{month:int}/{day:int}/{slug}"
@using Humanizer
-@using OliverBooth.Blog.Data
+@using OliverBooth.Data.Blog
@model Article
@if (Model.Post is not { } post)
@@ -47,9 +47,8 @@
}
-
- @* @Html.Raw(BlogService.GetContent(post)) *@
- @Html.Raw(post.Body)
+
+ Loading ...
diff --git a/OliverBooth.Blog/Pages/Article.cshtml.cs b/OliverBooth/Pages/Blog/Article.cshtml.cs
similarity index 94%
rename from OliverBooth.Blog/Pages/Article.cshtml.cs
rename to OliverBooth/Pages/Blog/Article.cshtml.cs
index 1c92762..caded85 100644
--- a/OliverBooth.Blog/Pages/Article.cshtml.cs
+++ b/OliverBooth/Pages/Blog/Article.cshtml.cs
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
-using OliverBooth.Blog.Data;
-using OliverBooth.Blog.Services;
+using OliverBooth.Data.Blog;
+using OliverBooth.Services;
-namespace OliverBooth.Blog.Pages;
+namespace OliverBooth.Pages.Blog;
///
/// Represents the page model for the Article page.
diff --git a/OliverBooth.Blog/Pages/Index.cshtml b/OliverBooth/Pages/Blog/Index.cshtml
similarity index 100%
rename from OliverBooth.Blog/Pages/Index.cshtml
rename to OliverBooth/Pages/Blog/Index.cshtml
diff --git a/OliverBooth.Blog/Pages/Index.cshtml.cs b/OliverBooth/Pages/Blog/Index.cshtml.cs
similarity index 93%
rename from OliverBooth.Blog/Pages/Index.cshtml.cs
rename to OliverBooth/Pages/Blog/Index.cshtml.cs
index 7101cbd..21b44ec 100644
--- a/OliverBooth.Blog/Pages/Index.cshtml.cs
+++ b/OliverBooth/Pages/Blog/Index.cshtml.cs
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
-using OliverBooth.Blog.Data;
-using OliverBooth.Blog.Services;
+using OliverBooth.Data.Blog;
+using OliverBooth.Services;
-namespace OliverBooth.Blog.Pages;
+namespace OliverBooth.Pages.Blog;
[Area("blog")]
public class Index : PageModel
diff --git a/OliverBooth.Blog/Pages/RawArticle.cshtml b/OliverBooth/Pages/Blog/RawArticle.cshtml
similarity index 100%
rename from OliverBooth.Blog/Pages/RawArticle.cshtml
rename to OliverBooth/Pages/Blog/RawArticle.cshtml
diff --git a/OliverBooth.Blog/Pages/RawArticle.cshtml.cs b/OliverBooth/Pages/Blog/RawArticle.cshtml.cs
similarity index 93%
rename from OliverBooth.Blog/Pages/RawArticle.cshtml.cs
rename to OliverBooth/Pages/Blog/RawArticle.cshtml.cs
index ee1f60f..1fc34eb 100644
--- a/OliverBooth.Blog/Pages/RawArticle.cshtml.cs
+++ b/OliverBooth/Pages/Blog/RawArticle.cshtml.cs
@@ -1,10 +1,10 @@
using Cysharp.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
-using OliverBooth.Blog.Data;
-using OliverBooth.Blog.Services;
+using OliverBooth.Data.Blog;
+using OliverBooth.Services;
-namespace OliverBooth.Blog.Pages;
+namespace OliverBooth.Pages.Blog;
///
/// Represents the page model for the RawArticle page.
diff --git a/OliverBooth.Common/Pages/Shared/_Layout.cshtml b/OliverBooth/Pages/Shared/_Layout.cshtml
similarity index 100%
rename from OliverBooth.Common/Pages/Shared/_Layout.cshtml
rename to OliverBooth/Pages/Shared/_Layout.cshtml
diff --git a/OliverBooth.Common/Pages/Shared/_LoadingSpinner.cshtml b/OliverBooth/Pages/Shared/_LoadingSpinner.cshtml
similarity index 100%
rename from OliverBooth.Common/Pages/Shared/_LoadingSpinner.cshtml
rename to OliverBooth/Pages/Shared/_LoadingSpinner.cshtml
diff --git a/OliverBooth/Program.cs b/OliverBooth/Program.cs
index 96307d7..65f6d98 100644
--- a/OliverBooth/Program.cs
+++ b/OliverBooth/Program.cs
@@ -1,7 +1,9 @@
-using OliverBooth.Common;
-using OliverBooth.Common.Extensions;
-using OliverBooth.Common.Services;
-using OliverBooth.Data;
+using Markdig;
+using OliverBooth.Data.Blog;
+using OliverBooth.Data.Web;
+using OliverBooth.Extensions;
+using OliverBooth.Markdown.Template;
+using OliverBooth.Markdown.Timestamp;
using OliverBooth.Services;
using Serilog;
@@ -15,15 +17,28 @@ builder.Configuration.AddTomlFile("data/config.toml", true, true);
builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
-builder.Services.AddMarkdownPipeline();
-builder.Services.ConfigureOptions();
-builder.Services.AddSingleton();
+builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder()
+ .Use()
+ .Use(new TemplateExtension(provider.GetRequiredService()))
+ .UseAdvancedExtensions()
+ .UseBootstrap()
+ .UseEmojiAndSmiley()
+ .UseSmartyPants()
+ .Build());
+
+builder.Services.AddDbContextFactory();
builder.Services.AddDbContextFactory();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllersWithViews();
builder.Services.AddRouting(options => options.LowercaseUrls = true);
-builder.WebHost.AddCertificateFromEnvironment(2845, 5049);
+if (builder.Environment.IsProduction())
+{
+ builder.WebHost.AddCertificateFromEnvironment(2845, 5049);
+}
WebApplication app = builder.Build();
diff --git a/OliverBooth.Blog/Services/BlogPostService.cs b/OliverBooth/Services/BlogPostService.cs
similarity index 65%
rename from OliverBooth.Blog/Services/BlogPostService.cs
rename to OliverBooth/Services/BlogPostService.cs
index 0f2d565..8a0f67e 100644
--- a/OliverBooth.Blog/Services/BlogPostService.cs
+++ b/OliverBooth/Services/BlogPostService.cs
@@ -1,8 +1,10 @@
using System.Diagnostics.CodeAnalysis;
+using Humanizer;
+using Markdig;
using Microsoft.EntityFrameworkCore;
-using OliverBooth.Blog.Data;
+using OliverBooth.Data.Blog;
-namespace OliverBooth.Blog.Services;
+namespace OliverBooth.Services;
///
/// Represents an implementation of .
@@ -10,7 +12,8 @@ namespace OliverBooth.Blog.Services;
internal sealed class BlogPostService : IBlogPostService
{
private readonly IDbContextFactory _dbContextFactory;
- private readonly IUserService _userService;
+ private readonly IBlogUserService _blogUserService;
+ private readonly MarkdownPipeline _markdownPipeline;
///
/// Initializes a new instance of the class.
@@ -18,20 +21,35 @@ internal sealed class BlogPostService : IBlogPostService
///
/// The used to create a .
///
- /// The .
- public BlogPostService(IDbContextFactory dbContextFactory, IUserService userService)
+ /// The .
+ /// The .
+ public BlogPostService(IDbContextFactory dbContextFactory,
+ IBlogUserService blogUserService,
+ MarkdownPipeline markdownPipeline)
{
_dbContextFactory = dbContextFactory;
- _userService = userService;
+ _blogUserService = blogUserService;
+ _markdownPipeline = markdownPipeline;
}
+ ///
+ public int GetBlogPostCount()
+ {
+ using BlogContext context = _dbContextFactory.CreateDbContext();
+ return context.BlogPosts.Count();
+ }
+
+ ///
public IReadOnlyList GetAllBlogPosts(int limit = -1)
{
using BlogContext context = _dbContextFactory.CreateDbContext();
- return context.BlogPosts
- .OrderByDescending(post => post.Published)
- .Take(limit)
- .AsEnumerable().Select(CacheAuthor).ToArray();
+ IQueryable ordered = context.BlogPosts.OrderByDescending(post => post.Published);
+ if (limit > -1)
+ {
+ ordered = ordered.Take(limit);
+ }
+
+ return ordered.AsEnumerable().Select(CacheAuthor).ToArray();
}
///
@@ -42,22 +60,30 @@ internal sealed class BlogPostService : IBlogPostService
.OrderByDescending(post => post.Published)
.Skip(page * pageSize)
.Take(pageSize)
- .AsEnumerable().Select(CacheAuthor).ToArray();
+ .ToArray().Select(CacheAuthor).ToArray();
}
///
public string RenderExcerpt(IBlogPost post, out bool wasTrimmed)
{
- // TODO implement excerpt trimming
- wasTrimmed = false;
- return post.Body;
+ 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)
{
- // TODO render markdown
- return post.Body;
+ return Markdig.Markdown.ToHtml(post.Body, _markdownPipeline);
}
///
@@ -114,7 +140,7 @@ internal sealed class BlogPostService : IBlogPostService
return post;
}
- if (_userService.TryGetUser(post.AuthorId, out IUser? user) && user is IBlogAuthor author)
+ if (_blogUserService.TryGetUser(post.AuthorId, out IUser? user) && user is IBlogAuthor author)
{
post.Author = author;
}
diff --git a/OliverBooth.Blog/Services/UserService.cs b/OliverBooth/Services/BlogUserService.cs
similarity index 66%
rename from OliverBooth.Blog/Services/UserService.cs
rename to OliverBooth/Services/BlogUserService.cs
index 023bd1e..09ed3ef 100644
--- a/OliverBooth.Blog/Services/UserService.cs
+++ b/OliverBooth/Services/BlogUserService.cs
@@ -1,23 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
-using OliverBooth.Blog.Data;
+using OliverBooth.Data.Blog;
-namespace OliverBooth.Blog.Services;
+namespace OliverBooth.Services;
///
-/// Represents an implementation of .
+/// Represents an implementation of .
///
-internal sealed class UserService : IUserService
+internal sealed class BlogUserService : IBlogUserService
{
private readonly IDbContextFactory _dbContextFactory;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
///
/// The used to create a .
///
- public UserService(IDbContextFactory dbContextFactory)
+ public BlogUserService(IDbContextFactory dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
diff --git a/OliverBooth.Blog/Services/IBlogPostService.cs b/OliverBooth/Services/IBlogPostService.cs
similarity index 94%
rename from OliverBooth.Blog/Services/IBlogPostService.cs
rename to OliverBooth/Services/IBlogPostService.cs
index fceb555..a7ac372 100644
--- a/OliverBooth.Blog/Services/IBlogPostService.cs
+++ b/OliverBooth/Services/IBlogPostService.cs
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
-using OliverBooth.Blog.Data;
+using OliverBooth.Data.Blog;
-namespace OliverBooth.Blog.Services;
+namespace OliverBooth.Services;
///
/// Represents a service for managing blog posts.
@@ -19,6 +19,12 @@ public interface IBlogPostService
///
IReadOnlyList GetAllBlogPosts(int limit = -1);
+ ///
+ /// Returns the total number of blog posts.
+ ///
+ /// The total number of blog posts.
+ int GetBlogPostCount();
+
///
/// Returns a collection of blog posts from the specified page, optionally limiting the number of posts
/// returned per page.
diff --git a/OliverBooth.Blog/Services/IUserService.cs b/OliverBooth/Services/IBlogUserService.cs
similarity index 87%
rename from OliverBooth.Blog/Services/IUserService.cs
rename to OliverBooth/Services/IBlogUserService.cs
index 40b89aa..0a117ae 100644
--- a/OliverBooth.Blog/Services/IUserService.cs
+++ b/OliverBooth/Services/IBlogUserService.cs
@@ -1,12 +1,12 @@
using System.Diagnostics.CodeAnalysis;
-using OliverBooth.Blog.Data;
+using OliverBooth.Data.Blog;
-namespace OliverBooth.Blog.Services;
+namespace OliverBooth.Services;
///
/// Represents a service for managing users.
///
-public interface IUserService
+public interface IBlogUserService
{
///
/// Attempts to find a user with the specified ID.
diff --git a/OliverBooth.Common/Services/ITemplateService.cs b/OliverBooth/Services/ITemplateService.cs
similarity index 88%
rename from OliverBooth.Common/Services/ITemplateService.cs
rename to OliverBooth/Services/ITemplateService.cs
index eacb31f..68f9829 100644
--- a/OliverBooth.Common/Services/ITemplateService.cs
+++ b/OliverBooth/Services/ITemplateService.cs
@@ -1,6 +1,6 @@
-using OliverBooth.Common.Markdown;
+using OliverBooth.Markdown.Template;
-namespace OliverBooth.Common.Services;
+namespace OliverBooth.Services;
///
/// Represents a service that renders MediaWiki-style templates.
diff --git a/OliverBooth/Services/TemplateService.cs b/OliverBooth/Services/TemplateService.cs
index 66365d1..474df8a 100644
--- a/OliverBooth/Services/TemplateService.cs
+++ b/OliverBooth/Services/TemplateService.cs
@@ -1,10 +1,8 @@
using System.Buffers.Binary;
using Microsoft.EntityFrameworkCore;
-using OliverBooth.Common.Formatting;
-using OliverBooth.Common.Markdown;
-using OliverBooth.Common.Services;
-using OliverBooth.Data;
-using OliverBooth.Data.Web;
+using OliverBooth.Data.Blog;
+using OliverBooth.Formatting;
+using OliverBooth.Markdown.Template;
using SmartFormat;
using SmartFormat.Extensions;
@@ -16,15 +14,15 @@ namespace OliverBooth.Services;
internal sealed class TemplateService : ITemplateService
{
private static readonly Random Random = new();
- private readonly IDbContextFactory _webContextFactory;
+ private readonly IDbContextFactory _webContextFactory;
private readonly SmartFormatter _formatter;
///
/// Initializes a new instance of the class.
///
/// The .
- /// The factory.
- public TemplateService(IServiceProvider serviceProvider, IDbContextFactory webContextFactory)
+ /// The factory.
+ public TemplateService(IServiceProvider serviceProvider, IDbContextFactory webContextFactory)
{
_formatter = Smart.CreateDefaultSmartFormat();
_formatter.AddExtensions(new DefaultSource());
@@ -40,7 +38,7 @@ internal sealed class TemplateService : ITemplateService
{
if (templateInline is null) throw new ArgumentNullException(nameof(templateInline));
- using WebContext webContext = _webContextFactory.CreateDbContext();
+ using BlogContext webContext = _webContextFactory.CreateDbContext();
Template? template = webContext.Templates.Find(templateInline.Name);
if (template is null)
{
diff --git a/docker-compose.yml b/docker-compose.yml
index 2cb053f..9668736 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,20 +16,6 @@ services:
ports:
- "2845:2845"
restart: always
-
- oliverbooth-blog:
- container_name: blog.oliverbooth.dev
- pull_policy: build
- build:
- context: .
- dockerfile: OliverBooth/Dockerfile
- volumes:
- - type: bind
- source: /var/log/oliverbooth/blog
- target: /app/logs
- - type: bind
- source: /etc/oliverbooth/blog
- target: /app/data
- ports:
- - "2846:2846"
- restart: always
+ environment:
+ - SSL_CERT_PATH=${SSL_CERT_PATH}
+ - SSL_KEY_PATH=${SSL_KEY_PATH}
diff --git a/src/ts/API.ts b/src/ts/API.ts
index cdcd965..f8b1aeb 100644
--- a/src/ts/API.ts
+++ b/src/ts/API.ts
@@ -2,7 +2,7 @@ import BlogPost from "./BlogPost";
import Author from "./Author";
class API {
- private static readonly BASE_URL: string = "https://api.oliverbooth.dev";
+ private static readonly BASE_URL: string = "/api";
private static readonly BLOG_URL: string = "/blog";
static async getBlogPostCount(): Promise {
diff --git a/src/ts/UI.ts b/src/ts/UI.ts
index 79ca54a..7744d27 100644
--- a/src/ts/UI.ts
+++ b/src/ts/UI.ts
@@ -7,6 +7,10 @@ declare const katex: any;
declare const Prism: any;
class UI {
+ public static get blogPost(): HTMLDivElement {
+ return document.querySelector("article[data-blog-post='true']");
+ }
+
public static get blogPostContainer(): HTMLDivElement {
return document.querySelector("#all-blog-posts");
}
diff --git a/src/ts/app.ts b/src/ts/app.ts
index 10d6136..420c99f 100644
--- a/src/ts/app.ts
+++ b/src/ts/app.ts
@@ -34,6 +34,15 @@ declare const Prism: any;
window.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "_blank");
});
+ const blogPost = UI.blogPost;
+ if (blogPost) {
+ const id = blogPost.dataset.blogId;
+ API.getBlogPost(id).then((post) => {
+ blogPost.innerHTML = post.content;
+ UI.updateUI(blogPost);
+ });
+ }
+
const blogPostContainer = UI.blogPostContainer;
if (blogPostContainer) {
const authors = [];