refactor!: move services and entities to common proj

This commit is contained in:
Oliver Booth 2024-03-02 00:43:56 +00:00
parent 652550a2fe
commit 1588f6c8f6
Signed by: oliverbooth
GPG Key ID: E60B570D1B7557B5
90 changed files with 301 additions and 193 deletions

View File

@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Blog.Configuration;
using Microsoft.Extensions.Configuration;
using OliverBooth.Common.Data.Blog.Configuration;
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <summary>
/// Represents a session with the blog database.

View File

@ -1,14 +1,14 @@
using System.ComponentModel.DataAnnotations.Schema;
using SmartFormat;
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <inheritdoc />
internal sealed class BlogPost : IBlogPost
{
/// <inheritdoc />
[NotMapped]
public IBlogAuthor Author { get; internal set; } = null!;
public IAuthor Author { get; internal set; } = null!;
/// <inheritdoc />
public string Body { get; set; } = string.Empty;

View File

@ -1,14 +1,14 @@
using System.ComponentModel.DataAnnotations.Schema;
using SmartFormat;
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <inheritdoc />
internal sealed class BlogPostDraft : IBlogPostDraft
{
/// <inheritdoc />
[NotMapped]
public IBlogAuthor Author { get; internal set; } = null!;
public IAuthor Author { get; internal set; } = null!;
/// <inheritdoc />
public string Body { get; set; } = string.Empty;

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <summary>
/// An enumeration of the possible visibilities of a blog post.

View File

@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace OliverBooth.Data.Blog.Configuration;
namespace OliverBooth.Common.Data.Blog.Configuration;
internal sealed class BlogPostConfiguration : IEntityTypeConfiguration<BlogPost>
{

View File

@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace OliverBooth.Data.Blog.Configuration;
namespace OliverBooth.Common.Data.Blog.Configuration;
internal sealed class BlogPostDraftConfiguration : IEntityTypeConfiguration<BlogPostDraft>
{

View File

@ -1,9 +1,9 @@
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <summary>
/// Represents the author of a blog post.
/// </summary>
public interface IBlogAuthor
public interface IAuthor
{
/// <summary>
/// Gets the URL of the author's avatar.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <summary>
/// Represents a blog post.
@ -9,7 +9,7 @@ public interface IBlogPost
/// Gets the author of the post.
/// </summary>
/// <value>The author of the post.</value>
IBlogAuthor Author { get; }
IAuthor Author { get; }
/// <summary>
/// Gets or sets the body of the post.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Blog;
namespace OliverBooth.Common.Data.Blog;
/// <summary>
/// Represents a draft of a blog post.
@ -9,7 +9,7 @@ public interface IBlogPostDraft
/// Gets the author of the post.
/// </summary>
/// <value>The author of the post.</value>
IBlogAuthor Author { get; }
IAuthor Author { get; }
/// <summary>
/// Gets or sets the body of the post.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data;
namespace OliverBooth.Common.Data;
/// <summary>
/// Represents a permission.

View File

@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace OliverBooth.Data;
namespace OliverBooth.Common.Data.ValueConverters;
internal sealed class PermissionListConverter : ValueConverter<IReadOnlyList<Permission>, string>
{

View File

@ -3,7 +3,7 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Processing;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Books;
/// <summary>
/// Represents a book.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Books;
/// <summary>
/// Represents the state of a book.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Books;
/// <summary>
/// Represents a book.

View File

@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OliverBooth.Common.Data.Web.Contact;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="BlacklistEntry" /> entity.

View File

@ -1,8 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OliverBooth.Common.Data.Web.Books;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="Book" /> entity.

View File

@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OliverBooth.Common.Data.Web.Projects;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="ProgrammingLanguage" /> entity.

View File

@ -1,8 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OliverBooth.Common.Data.Web.Projects;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="Project" /> entity.

View File

@ -1,8 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OliverBooth.Common.Data.Web.Users;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
internal sealed class SessionConfiguration : IEntityTypeConfiguration<Session>
{

View File

@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="SiteConfiguration" /> entity.

View File

@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
/// <summary>
/// Represents the configuration for the <see cref="Template" /> entity.

View File

@ -1,7 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OliverBooth.Common.Data.ValueConverters;
using OliverBooth.Common.Data.Web.Users;
namespace OliverBooth.Data.Web.Configuration;
namespace OliverBooth.Common.Data.Web.Configuration;
internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
{

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Contact;
/// <inheritdoc cref="IBlacklistEntry"/>
internal sealed class BlacklistEntry : IEquatable<BlacklistEntry>, IBlacklistEntry

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Contact;
/// <summary>
/// Represents an entry in the blacklist.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web;
/// <summary>
/// Represents a template.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Projects;
/// <summary>
/// Represents a programming language.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Projects;
/// <summary>
/// Represents a project.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Projects;
/// <inheritdoc cref="IProgrammingLanguage" />
internal sealed class ProgrammingLanguage : IEquatable<ProgrammingLanguage>, IProgrammingLanguage

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Projects;
/// <summary>
/// Represents a project.

View File

@ -1,6 +1,6 @@
using System.ComponentModel;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Projects;
/// <summary>
/// Represents the status of a project.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web;
/// <summary>
/// Represents a site configuration item.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web;
/// <summary>
/// Represents a MediaWiki-style template.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
/// <summary>
/// Represents a temporary token used to correlate MFA attempts with the user.
@ -34,4 +34,4 @@ public interface IMfaToken
/// </summary>
/// <value>The user.</value>
IUser User { get; }
}
}

View File

@ -1,6 +1,6 @@
using System.Net;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
/// <summary>
/// Represents a login session.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
/// <summary>
/// Represents a user which can log in to the blog.

View File

@ -1,6 +1,6 @@
using OliverBooth.Services;
using OliverBooth.Common.Services;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
/// <summary>
/// An enumeration of possible results for <see cref="IUserService.VerifyMfaRequest" />.

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
internal sealed class MfaToken : IMfaToken
{

View File

@ -1,6 +1,6 @@
using System.Net;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
internal sealed class Session : ISession
{

View File

@ -2,15 +2,15 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using System.Text;
using Cysharp.Text;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
using OtpNet;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web.Users;
/// <summary>
/// Represents a user.
/// </summary>
internal sealed class User : IUser, IBlogAuthor
internal sealed class User : IUser, IAuthor
{
/// <inheritdoc cref="IUser.AvatarUrl" />
[NotMapped]

View File

@ -1,7 +1,12 @@
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web.Configuration;
using Microsoft.Extensions.Configuration;
using OliverBooth.Common.Data.Web.Books;
using OliverBooth.Common.Data.Web.Configuration;
using OliverBooth.Common.Data.Web.Contact;
using OliverBooth.Common.Data.Web.Projects;
using OliverBooth.Common.Data.Web.Users;
namespace OliverBooth.Data.Web;
namespace OliverBooth.Common.Data.Web;
/// <summary>
/// Represents a session with the web database.

View File

@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Services;
using X10D.Hosting.DependencyInjection;
namespace OliverBooth.Common.Extensions;
/// <summary>
/// Extension methods for dependency injection.
/// </summary>
public static class DependencyInjectionExtensions
{
/// <summary>
/// Adds all required services provided by the assembly to the current <see cref="IServiceCollection" />.
/// </summary>
/// <param name="collection">The <see cref="IServiceCollection" /> to add the service to.</param>
public static void AddCommonServices(this IServiceCollection collection)
{
collection.AddDbContextFactory<BlogContext>();
collection.AddDbContextFactory<WebContext>();
collection.AddSingleton<IBlogPostService, BlogPostService>();
collection.AddSingleton<IContactService, ContactService>();
collection.AddSingleton<IProjectService, ProjectService>();
collection.AddSingleton<IReadingListService, ReadingListService>();
collection.AddSingleton<ITemplateService, TemplateService>();
collection.AddHostedSingleton<ISessionService, SessionService>();
collection.AddHostedSingleton<IUserService, UserService>();
}
}

View File

@ -1,7 +1,7 @@
using System.Globalization;
using SmartFormat.Core.Extensions;
namespace OliverBooth.Formatting;
namespace OliverBooth.Common.Formatting;
/// <summary>
/// Represents a SmartFormat formatter that formats a date.

View File

@ -1,7 +1,8 @@
using Markdig;
using Microsoft.Extensions.DependencyInjection;
using SmartFormat.Core.Extensions;
namespace OliverBooth.Formatting;
namespace OliverBooth.Common.Formatting;
/// <summary>
/// Represents a SmartFormat formatter that formats markdown.

View File

@ -1,8 +1,8 @@
using Markdig;
using Markdig.Renderers;
using OliverBooth.Services;
using OliverBooth.Common.Services;
namespace OliverBooth.Markdown.Template;
namespace OliverBooth.Common.Markdown.Template;
/// <summary>
/// Represents a Markdown extension that adds support for MediaWiki-style templates.

View File

@ -1,6 +1,6 @@
using Markdig.Syntax.Inlines;
namespace OliverBooth.Markdown.Template;
namespace OliverBooth.Common.Markdown.Template;
/// <summary>
/// Represents a Markdown inline element that represents a MediaWiki-style template.

View File

@ -2,7 +2,7 @@ using Cysharp.Text;
using Markdig.Helpers;
using Markdig.Parsers;
namespace OliverBooth.Markdown.Template;
namespace OliverBooth.Common.Markdown.Template;
/// <summary>
/// Represents a Markdown inline parser that handles MediaWiki-style templates.
@ -17,7 +17,7 @@ public sealed class TemplateInlineParser : InlineParser
/// </summary>
public TemplateInlineParser()
{
OpeningCharacters = new[] { '{' };
OpeningCharacters = ['{'];
}
/// <inheritdoc />

View File

@ -1,8 +1,8 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
using OliverBooth.Services;
using OliverBooth.Common.Services;
namespace OliverBooth.Markdown.Template;
namespace OliverBooth.Common.Markdown.Template;
/// <summary>
/// Represents a Markdown object renderer that handles <see cref="TemplateInline" /> elements.

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0"/>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
<PackageReference Include="Markdig" Version="0.35.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
<PackageReference Include="NetBarcode" Version="1.7.0"/>
<PackageReference Include="Otp.NET" Version="1.3.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0"/>
<PackageReference Include="Serilog" Version="3.1.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
<PackageReference Include="X10D" Version="3.3.1"/>
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
<PackageReference Include="ZString" Version="2.5.1"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Markdown\"/>
</ItemGroup>
</Project>

View File

@ -2,16 +2,28 @@ using System.Diagnostics.CodeAnalysis;
using Humanizer;
using Markdig;
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Blog;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Data.Web.Users;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents an implementation of <see cref="IBlogPostService" />.
/// </summary>
internal sealed class BlogPostService : IBlogPostService
{
/*private static readonly JsonSerializerOptions EditorJsOptions = new()
{
ReferenceHandler = ReferenceHandler.Preserve,
Converters =
{
new ParagraphBlockConverter(),
new HeadingBlockConverter(),
new MarkdownDocumentConverter()
}
};
*/
private readonly IDbContextFactory<BlogContext> _dbContextFactory;
private readonly IUserService _userService;
private readonly MarkdownPipeline _markdownPipeline;
@ -33,6 +45,19 @@ internal sealed class BlogPostService : IBlogPostService
_markdownPipeline = markdownPipeline;
}
/// <inheritdoc />
public string GetBlogPostEditorObject(IBlogPost post)
{
if (post is null)
{
throw new ArgumentNullException(nameof(post));
}
/*var document = (JsonDocument)Markdig.Markdown.Convert(post.Body, new JsonRenderer(), _markdownPipeline);
return JsonSerializer.Serialize(document, EditorJsOptions);*/
return """{"blocks":{}}""";
}
/// <inheritdoc />
public int GetBlogPostCount()
{
@ -196,7 +221,7 @@ internal sealed class BlogPostService : IBlogPostService
return post;
}
if (_userService.TryGetUser(post.AuthorId, out IUser? user) && user is IBlogAuthor author)
if (_userService.TryGetUser(post.AuthorId, out IUser? user) && user is IAuthor author)
{
post.Author = author;
}

View File

@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Data.Web.Contact;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <inheritdoc cref="IContactService" />
internal sealed class ContactService : IContactService

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service for managing blog posts.
@ -27,6 +27,14 @@ public interface IBlogPostService
/// <returns>The total number of blog posts.</returns>
int GetBlogPostCount();
/// <summary>
/// Returns a JSON object representing the blog post block data.
/// </summary>
/// <param name="post">The blog post whose block data object should be returned.</param>
/// <returns>The JSON data of the blog post block data.</returns>
/// <exception cref="ArgumentNullException"><paramref name="post" /> is <see langword="null" />.</exception>
string GetBlogPostEditorObject(IBlogPost post);
/// <summary>
/// Returns a collection of blog posts from the specified page, optionally limiting the number of posts
/// returned per page.

View File

@ -1,6 +1,6 @@
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web.Contact;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service for managing contact information.

View File

@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web.Projects;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service for interacting with projects.

View File

@ -1,6 +1,6 @@
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web.Books;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service which fetches books from the reading list.

View File

@ -1,9 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OliverBooth.Data.Web;
using ISession = OliverBooth.Data.Web.ISession;
using OliverBooth.Common.Data.Web.Users;
using ISession = OliverBooth.Common.Data.Web.Users.ISession;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
public interface ISessionService
{

View File

@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data.Web;
using OliverBooth.Markdown.Template;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Markdown.Template;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service that renders MediaWiki-style templates.

View File

@ -1,10 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web.Users;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service for managing users.
/// Represents a service which manages users.
/// </summary>
public interface IUserService
{
@ -34,15 +34,16 @@ public interface IUserService
void DeleteToken(string token);
/// <summary>
/// Attempts to find a user with the specified ID.
/// Attempts to find a user by their unique ID.
/// </summary>
/// <param name="id">The ID of the user to find.</param>
/// <param name="id">The ID of the user to return.</param>
/// <param name="user">
/// When this method returns, contains the user with the specified ID, if the user is found; otherwise,
/// <see langword="null" />.
/// When this method returns, contains the user whose ID is equal to the specified <paramref name="id" />, if
/// such a user exists; otherwise, <see langword="null" />.
/// </param>
/// <returns>
/// <see langword="true" /> if a user with the specified ID is found; otherwise, <see langword="false" />.
/// <see langword="true" /> if a user was found with the specified <paramref name="id" />; otherwise,
/// <see langword="false" />.
/// </returns>
bool TryGetUser(Guid id, [NotNullWhen(true)] out IUser? user);

View File

@ -2,9 +2,10 @@ using System.Diagnostics.CodeAnalysis;
using Humanizer;
using Markdig;
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Data.Web.Projects;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service for interacting with projects.

View File

@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Data.Web.Books;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
internal sealed class ReadingListService : IReadingListService
{

View File

@ -1,13 +1,16 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using OliverBooth.Data.Blog;
using OliverBooth.Data.Web;
using ISession = OliverBooth.Data.Web.ISession;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Data.Web.Users;
using ISession = OliverBooth.Common.Data.Web.Users.ISession;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
internal sealed class SessionService : BackgroundService, ISessionService
{
@ -20,11 +23,9 @@ internal sealed class SessionService : BackgroundService, ISessionService
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="userService">The user service.</param>
/// <param name="blogContextFactory">The <see cref="BlogContext" /> factory.</param>
/// <param name="webContextFactory">The <see cref="WebContext" /> factory.</param>
public SessionService(ILogger<SessionService> logger,
IUserService userService,
IDbContextFactory<BlogContext> blogContextFactory,
IDbContextFactory<WebContext> webContextFactory)
{
_logger = logger;

View File

@ -1,13 +1,13 @@
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web;
using OliverBooth.Formatting;
using OliverBooth.Markdown.Template;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Formatting;
using OliverBooth.Common.Markdown.Template;
using SmartFormat;
using SmartFormat.Extensions;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents a service that renders MediaWiki-style templates.
@ -22,7 +22,7 @@ internal sealed class TemplateService : ITemplateService
/// Initializes a new instance of the <see cref="TemplateService" /> class.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider" />.</param>
/// <param name="webContextFactory">The <see cref="WebContext" /> factory.</param>
/// <param name="webContextFactory">The <see cref="Data.Web.WebContext" /> factory.</param>
public TemplateService(IServiceProvider serviceProvider,
IDbContextFactory<WebContext> webContextFactory)
{

View File

@ -2,15 +2,14 @@ using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data.Web;
using Microsoft.Extensions.Hosting;
using OliverBooth.Common.Data.Web;
using OliverBooth.Common.Data.Web.Users;
using BC = BCrypt.Net.BCrypt;
using Timer = System.Timers.Timer;
namespace OliverBooth.Services;
namespace OliverBooth.Common.Services;
/// <summary>
/// Represents an implementation of <see cref="IUserService" />.
/// </summary>
internal sealed class UserService : BackgroundService, IUserService
{
private static readonly RandomNumberGenerator RandomNumberGenerator = RandomNumberGenerator.Create();
@ -117,19 +116,8 @@ internal sealed class UserService : BackgroundService, IUserService
/// <inheritdoc />
public bool TryGetUser(Guid id, [NotNullWhen(true)] out IUser? user)
{
if (_userCache.TryGetValue(id, out user))
{
return true;
}
using WebContext context = _dbContextFactory.CreateDbContext();
user = context.Users.Find(id);
if (user is not null)
{
_userCache.TryAdd(id, user);
}
user = context.Users.FirstOrDefault(u => u.Id == id);
return user is not null;
}
@ -138,7 +126,12 @@ internal sealed class UserService : BackgroundService, IUserService
{
using WebContext context = _dbContextFactory.CreateDbContext();
user = context.Users.FirstOrDefault(u => u.EmailAddress == email);
return user is not null && BC.Verify(password, ((User)user).Password);
if (user is not null && !BC.Verify(password, ((User)user).Password))
{
user = null;
}
return user is not null;
}
/// <inheritdoc />

View File

@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Gulpfile.mjs = Gulpfile.mjs
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliverBooth.Common", "OliverBooth.Common\OliverBooth.Common.csproj", "{77DC9941-E648-442F-935A-C66FC401ECBC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -23,6 +25,10 @@ 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
{77DC9941-E648-442F-935A-C66FC401ECBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77DC9941-E648-442F-935A-C66FC401ECBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77DC9941-E648-442F-935A-C66FC401ECBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77DC9941-E648-442F-935A-C66FC401ECBC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection

View File

@ -1,9 +1,9 @@
using System.Diagnostics;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using ISession = OliverBooth.Data.Web.ISession;
using OliverBooth.Common.Data.Web.Users;
using OliverBooth.Common.Services;
using ISession = OliverBooth.Common.Data.Web.Users.ISession;
namespace OliverBooth.Controllers.Api.v1;

View File

@ -1,8 +1,9 @@
using Asp.Versioning;
using Humanizer;
using Microsoft.AspNetCore.Mvc;
using OliverBooth.Data.Blog;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Data.Web.Users;
using OliverBooth.Common.Services;
using OliverBooth.Services;
namespace OliverBooth.Controllers.Api.v1;

View File

@ -1,6 +1,7 @@
using System.Xml.Serialization;
using Microsoft.AspNetCore.Mvc;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Services;
using OliverBooth.Data.Blog.Rss;
using OliverBooth.Services;

View File

@ -1,7 +1,7 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using OliverBooth.Common.Data.Web.Contact;
using OliverBooth.Common.Services;
namespace OliverBooth.Controllers;

View File

@ -13,7 +13,7 @@ public sealed class TimestampInlineParser : InlineParser
/// </summary>
public TimestampInlineParser()
{
OpeningCharacters = new[] { '<' };
OpeningCharacters = ['<'];
}
/// <inheritdoc />

View File

@ -27,32 +27,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Alexinea.Extensions.Configuration.Toml" Version="7.0.0"/>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0"/>
<PackageReference Include="AspNetCore.ReCaptcha" Version="1.8.1"/>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
<PackageReference Include="FluentFTP" Version="49.0.2" />
<PackageReference Include="FluentFTP.Logging" Version="1.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.59"/>
<PackageReference Include="Humanizer.Core" Version="2.14.1"/>
<PackageReference Include="MailKit" Version="4.3.0"/>
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
<PackageReference Include="Markdig" Version="0.35.0"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.2"/>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.2"/>
<PackageReference Include="NetBarcode" Version="1.7.0"/>
<PackageReference Include="Otp.NET" Version="1.3.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0"/>
<PackageReference Include="Serilog" Version="3.1.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1"/>
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
<PackageReference Include="SmartFormat.NET" Version="3.3.2"/>
<PackageReference Include="X10D" Version="3.3.1"/>
<PackageReference Include="X10D.Hosting" Version="3.3.1"/>
<PackageReference Include="ZString" Version="2.5.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OliverBooth.Common\OliverBooth.Common.csproj"/>
</ItemGroup>
</Project>

View File

@ -1,8 +1,10 @@
@page "/admin/blog-posts"
@using System.Diagnostics
@using OliverBooth.Data
@using OliverBooth.Data.Blog
@using OliverBooth.Data.Web
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using OliverBooth.Common.Data
@using OliverBooth.Common.Data.Blog
@using OliverBooth.Common.Data.Web.Users
@using OliverBooth.Common.Services
@using OliverBooth.Services
@model OliverBooth.Pages.Admin.BlogPosts
@inject IBlogPostService BlogPostService
@ -52,7 +54,7 @@
<th>Options</th>
</tr>
</thead>
<tbody>
@foreach (IBlogPost post in BlogPostService.GetAllBlogPosts(visibility: (BlogPostVisibility)(-1)))
{

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using OliverBooth.Common.Data.Web.Users;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages.Admin;

View File

@ -1,6 +1,6 @@
@page "/admin/blog-posts/edit/{id}"
@using Markdig
@using OliverBooth.Data.Blog
@using OliverBooth.Common.Data.Blog
@model OliverBooth.Pages.Admin.EditBlogPost
@inject MarkdownPipeline MarkdownPipeline

View File

@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Blog;
using OliverBooth.Data.Web;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Data.Web.Users;
using OliverBooth.Common.Services;
using OliverBooth.Services;
namespace OliverBooth.Pages.Admin;

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using OliverBooth.Common.Data.Web.Users;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages.Admin;

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Services;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages.Admin;

View File

@ -1,5 +1,5 @@
@page "/admin/login/mfa"
@using OliverBooth.Data.Web
@using OliverBooth.Common.Data.Web.Users
@model OliverBooth.Pages.Admin.MultiFactorStep
@{

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Services;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages.Admin;

View File

@ -1,6 +1,7 @@
@page "/blog/{year:int}/{month:int}/{day:int}/{slug}"
@using Humanizer
@using OliverBooth.Data.Blog
@using OliverBooth.Common.Data.Blog
@using OliverBooth.Common.Services
@using OliverBooth.Services
@inject IBlogPostService BlogPostService
@model Article
@ -29,7 +30,7 @@
@{
ViewData["Post"] = post;
ViewData["Title"] = post.Title;
IBlogAuthor author = post.Author;
IAuthor author = post.Author;
DateTimeOffset published = post.Published;
}

View File

@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Primitives;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Services;
using OliverBooth.Services;
using BC = BCrypt.Net.BCrypt;

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Services;
using OliverBooth.Services;
namespace OliverBooth.Pages.Blog;

View File

@ -1,7 +1,8 @@
using Cysharp.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Blog;
using OliverBooth.Common.Data.Blog;
using OliverBooth.Common.Services;
using OliverBooth.Services;
namespace OliverBooth.Pages.Blog;

View File

@ -1,5 +1,5 @@
@page
@using OliverBooth.Data.Web
@using OliverBooth.Common.Data.Web.Books
@model OliverBooth.Pages.Books
@{
ViewData["Title"] = "Reading List";

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using OliverBooth.Common.Data.Web.Books;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages;

View File

@ -1,6 +1,6 @@
@page
@using OliverBooth.Data.Web
@using OliverBooth.Services
@using OliverBooth.Common.Data.Web.Contact
@using OliverBooth.Common.Services
@inject IContactService ContactService
@{
ViewData["Title"] = "Blacklist";

View File

@ -1,6 +1,6 @@
@page
@using OliverBooth.Data.Web
@using OliverBooth.Services
@using OliverBooth.Common.Data.Web.Projects
@using OliverBooth.Common.Services
@inject IProjectService ProjectService
@{
ViewData["Title"] = "Projects";

View File

@ -1,7 +1,7 @@
@page "/project/{slug}"
@using Markdig
@using OliverBooth.Data.Web
@using OliverBooth.Services
@using OliverBooth.Common.Data.Web.Projects
@using OliverBooth.Common.Services
@model Project
@inject IProjectService ProjectService
@inject MarkdownPipeline MarkdownPipeline

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using OliverBooth.Data.Web;
using OliverBooth.Services;
using OliverBooth.Common.Data.Web.Projects;
using OliverBooth.Common.Services;
namespace OliverBooth.Pages.Projects;

View File

@ -1,6 +1,8 @@
@using OliverBooth.Data.Blog
@using OliverBooth.Data.Web
@using OliverBooth.Services
@using OliverBooth.Common.Data.Web.Users
@using OliverBooth.Common.Services
@using OliverBooth.Common.Data.Blog
@inject IBlogPostService BlogPostService
@inject IUserService UserService
@inject ISessionService SessionService

View File

@ -1,6 +1,8 @@
@using OliverBooth.Data.Blog
@using OliverBooth.Data.Web
@using OliverBooth.Services
@using OliverBooth.Common.Data.Web.Users
@using OliverBooth.Common.Services
@using OliverBooth.Common.Data.Blog
@inject IBlogPostService BlogPostService
@inject IUserService UserService
@inject ISessionService SessionService

View File

@ -4,15 +4,15 @@ using AspNetCore.ReCaptcha;
using FluentFTP;
using FluentFTP.Logging;
using Markdig;
using OliverBooth.Common.Extensions;
using OliverBooth.Common.Markdown.Template;
using OliverBooth.Common.Services;
using OliverBooth.Data.Blog;
using OliverBooth.Data.Web;
using OliverBooth.Extensions;
using OliverBooth.Markdown.Template;
using OliverBooth.Markdown.Timestamp;
using OliverBooth.Services;
using Serilog;
using Serilog.Extensions.Logging;
using X10D.Hosting.DependencyInjection;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
@ -44,8 +44,7 @@ builder.Services.AddApiVersioning(options =>
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
builder.Services.AddDbContextFactory<BlogContext>();
builder.Services.AddDbContextFactory<WebContext>();
builder.Services.AddCommonServices();
builder.Services.AddHttpClient();
builder.Services.AddTransient<IAsyncFtpClient, AsyncFtpClient>(provider =>
{
@ -66,14 +65,7 @@ builder.Services.AddTransient<IAsyncFtpClient, AsyncFtpClient>(provider =>
});
builder.Services.AddSingleton<ICdnService, CdnService>();
builder.Services.AddSingleton<IContactService, ContactService>();
builder.Services.AddSingleton<ITemplateService, TemplateService>();
builder.Services.AddSingleton<IBlogPostService, BlogPostService>();
builder.Services.AddSingleton<IProjectService, ProjectService>();
builder.Services.AddSingleton<IMastodonService, MastodonService>();
builder.Services.AddSingleton<IReadingListService, ReadingListService>();
builder.Services.AddHostedSingleton<IUserService, UserService>();
builder.Services.AddHostedSingleton<ISessionService, SessionService>();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorComponents().AddInteractiveServerComponents();