From 8fbd10caaac8992567e8ca3f8cd954f8f1d4bb90 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 00:37:13 +0100 Subject: [PATCH] Update admin project to use new IAliasServerDbContextFactory (#190) --- src/AliasVault.Admin/Main/Pages/MainBase.cs | 4 +- src/AliasVault.Admin/Program.cs | 4 +- src/AliasVault.Admin/appsettings.json | 1 + .../AliasServerDb/AliasServerDbContext.cs | 6 +-- .../Configuration/DatabaseConfiguration.cs | 41 ++++++---------- .../IAliasServerDbContextFactory.cs | 27 +++++++++++ .../PostgresqlDbContextFactory.cs | 47 ++++++++++++++++++ .../AliasServerDb/SqliteDbContextFactory.cs | 48 +++++++++++++++++++ .../Services/ServerSettingsService.cs | 8 ++-- 9 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs create mode 100644 src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs create mode 100644 src/Databases/AliasServerDb/SqliteDbContextFactory.cs diff --git a/src/AliasVault.Admin/Main/Pages/MainBase.cs b/src/AliasVault.Admin/Main/Pages/MainBase.cs index c72d559c4..b4431113d 100644 --- a/src/AliasVault.Admin/Main/Pages/MainBase.cs +++ b/src/AliasVault.Admin/Main/Pages/MainBase.cs @@ -56,10 +56,10 @@ public abstract class MainBase : OwningComponentBase protected AliasServerDbContext DbContext { get; set; } = null!; /// - /// Gets or sets the AliasServerDbContextFactory instance. + /// Gets or sets the IAliasServerDbContextFactory instance. /// [Inject] - protected IDbContextFactory DbContextFactory { get; set; } = null!; + protected IAliasServerDbContextFactory DbContextFactory { get; set; } = null!; /// /// Gets or sets the GlobalLoadingService in order to manipulate the global loading spinner animation. diff --git a/src/AliasVault.Admin/Program.cs b/src/AliasVault.Admin/Program.cs index 876f4a558..1e272a766 100644 --- a/src/AliasVault.Admin/Program.cs +++ b/src/AliasVault.Admin/Program.cs @@ -69,7 +69,7 @@ builder.Services.ConfigureApplicationCookie(options => options.LoginPath = "/user/login"; }); -builder.Services.AddAliasVaultSqliteConfiguration(); +builder.Services.AddAliasVaultDatabaseConfiguration(builder.Configuration); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentityCore(options => { @@ -133,7 +133,7 @@ app.MapRazorComponents() using (var scope = app.Services.CreateScope()) { var container = scope.ServiceProvider; - await using var db = await container.GetRequiredService>().CreateDbContextAsync(); + await using var db = container.GetRequiredService().CreateDbContext(); await db.Database.MigrateAsync(); await StartupTasks.CreateRolesIfNotExist(scope.ServiceProvider); diff --git a/src/AliasVault.Admin/appsettings.json b/src/AliasVault.Admin/appsettings.json index a07244645..bc79ae18f 100644 --- a/src/AliasVault.Admin/appsettings.json +++ b/src/AliasVault.Admin/appsettings.json @@ -1,4 +1,5 @@ { + "DatabaseProvider": "sqlite", "ConnectionStrings": { "AliasServerDbContext": "Data Source=../../database/AliasServerDb.sqlite" }, diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index 24a3051dc..ae1468bdb 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -18,12 +18,12 @@ using Microsoft.Extensions.Configuration; /// we have two separate user objects, one for the admin panel and one for the vault. We manually /// define the Identity tables in the OnModelCreating method. /// -public abstract class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext +public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext { /// /// Initializes a new instance of the class. /// - protected AliasServerDbContext() + public AliasServerDbContext() { } @@ -31,7 +31,7 @@ public abstract class AliasServerDbContext : WorkerStatusDbContext, IDataProtect /// Initializes a new instance of the class. /// /// DbContextOptions. - protected AliasServerDbContext(DbContextOptions options) + public AliasServerDbContext(DbContextOptions options) : base(options) { } diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index 448a91e68..2d4a67b72 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -7,9 +7,6 @@ namespace AliasServerDb.Configuration; -using System.Data.Common; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,39 +19,29 @@ public static class DatabaseConfiguration /// Configures SQLite for use with Entity Framework Core. /// /// The IServiceCollection to add the DbContext to. + /// The IConfiguration to use for the connection string. /// The IServiceCollection for method chaining. - public static IServiceCollection AddAliasVaultSqliteConfiguration(this IServiceCollection services) + public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServiceCollection services, IConfiguration configuration) { - var serviceProvider = services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); + var dbProvider = configuration.GetValue("DatabaseProvider")?.ToLower() ?? "sqlite"; - var connectionString = configuration.GetConnectionString("AliasServerDbContext"); - if (string.IsNullOrEmpty(connectionString)) + switch (dbProvider) { - throw new InvalidOperationException("Connection string 'AliasServerDbContext' not found."); + case "postgresql": + services.AddScoped(); + break; + case "sqlite": + default: + services.AddScoped(); + break; } - var sqliteConnectionStringBuilder = new SqliteConnectionStringBuilder(connectionString) + services.AddScoped(sp => { - Cache = SqliteCacheMode.Private, - Mode = SqliteOpenMode.ReadWriteCreate, - }; - - services.AddDbContextFactory(options => - { - options.UseSqlite(CreateAndConfigureSqliteConnection(sqliteConnectionStringBuilder.ConnectionString), sqliteOptions => - { - sqliteOptions.CommandTimeout(60); - }).UseLazyLoadingProxies(); + var factory = sp.GetRequiredService(); + return factory.CreateDbContext(); }); return services; } - - private static SqliteConnection CreateAndConfigureSqliteConnection(string connectionString) - { - var connection = new SqliteConnection(connectionString); - connection.Open(); - return connection; - } } diff --git a/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs new file mode 100644 index 000000000..3b19af1cd --- /dev/null +++ b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +/// +/// The AliasServerDbContextFactory interface. +/// +public interface IAliasServerDbContextFactory +{ + /// + /// Creates a new AliasServerDbContext. + /// + /// The AliasServerDbContext. + AliasServerDbContext CreateDbContext(); + + /// + /// Creates a new AliasServerDbContext asynchronously. + /// + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the AliasServerDbContext. + Task CreateDbContextAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs new file mode 100644 index 000000000..262d040d1 --- /dev/null +++ b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// The AliasServerDbContextFactory interface. +/// +public class PostgresqlDbContextFactory : IAliasServerDbContextFactory +{ + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public PostgresqlDbContextFactory(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public AliasServerDbContext CreateDbContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + var connectionString = _configuration.GetConnectionString("AliasServerDbContext"); + + optionsBuilder + .UseNpgsql(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + + return new AliasServerDbContextPostgresql(optionsBuilder.Options); + } + + /// + public Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(CreateDbContext()); + } +} diff --git a/src/Databases/AliasServerDb/SqliteDbContextFactory.cs b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs new file mode 100644 index 000000000..69f08b50a --- /dev/null +++ b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// The AliasServerDbContextFactory interface. +/// +public class SqliteDbContextFactory : IAliasServerDbContextFactory +{ + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public SqliteDbContextFactory(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public AliasServerDbContext CreateDbContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + var connectionString = _configuration.GetConnectionString("AliasServerDbContext") + + ";Mode=ReadWriteCreate;Cache=Shared"; + + optionsBuilder + .UseSqlite(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + + return new AliasServerDbContextSqlite(optionsBuilder.Options); + } + + /// + public Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(CreateDbContext()); + } +} diff --git a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs index 5ac7622f1..c00305141 100644 --- a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs +++ b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs @@ -20,7 +20,7 @@ using Microsoft.EntityFrameworkCore; /// Server settings service. /// /// IDbContextFactory instance. -public class ServerSettingsService(IDbContextFactory dbContextFactory) +public class ServerSettingsService(IAliasServerDbContextFactory dbContextFactory) { private readonly Dictionary _cache = new(); @@ -36,7 +36,7 @@ public class ServerSettingsService(IDbContextFactory dbCon return cachedValue; } - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); _cache[key] = setting?.Value; @@ -57,7 +57,7 @@ public class ServerSettingsService(IDbContextFactory dbCon return; } - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); var now = DateTime.UtcNow; @@ -96,7 +96,7 @@ public class ServerSettingsService(IDbContextFactory dbCon /// The settings. public async Task GetAllSettingsAsync() { - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var settings = await dbContext.ServerSettings.ToDictionaryAsync(x => x.Key, x => x.Value); // Create model with defaults