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 = [];