feat: added database

This commit is contained in:
Greboh committed 2026-11-07 18:04:02 +01:00
1 parent 4c517788fe
commit 91ae771ca2
7 files changed
+107 -1

No files matched your search

@@ -0,0 +1,43 @@
using System.Reflection;
using DbUp;
using Microsoft.Extensions.Hosting;
using Npgsql;
namespace Hearth.Database;
// A hosted service so building the app (e.g. a schema export in CI) needs no database.
// Not safe for two processes upgrading the same schema at once; Hearth.Api runs one replica with Recreate until the worker split.
internal sealed class DatabaseUpgrade(
string connectionString,
string schema,
Assembly scripts
) : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
using (var connection = new NpgsqlConnection(connectionString))
{
connection.Open();
using var command = new NpgsqlCommand($"CREATE SCHEMA IF NOT EXISTS {schema}", connection);
command.ExecuteNonQuery();
}
var result = DeployChanges.To
.PostgresqlDatabase(connectionString)
.JournalToPostgresqlTable(schema, "schema_versions")
.WithScriptsEmbeddedInAssembly(scripts)
.WithTransactionPerScript()
.LogToConsole()
.Build()
.PerformUpgrade();
if (!result.Successful)
{
throw new InvalidOperationException($"Database upgrade of schema '{schema}' failed.", result.Error);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Hearth.Database.Extensions;
public static class ServiceCollectionExtensions
{
private const string ConnectionStringName = "Postgres";
// Registers a module's DbContext and upgrades its schema at startup with the SQL scripts embedded in the context's assembly.
public static IServiceCollection AddHearthDatabase<TContext>(this IServiceCollection services, string schema)
where TContext : DbContext
{
services.AddPooledDbContextFactory<TContext>((provider, options) => options
.UseNpgsql(ConnectionString(provider)));
services.AddScoped(provider => provider
.GetRequiredService<IDbContextFactory<TContext>>()
.CreateDbContext());
services.AddHostedService(provider => new DatabaseUpgrade(
ConnectionString(provider),
schema,
typeof(TContext).Assembly
));
return services;
}
private static string ConnectionString(IServiceProvider provider) =>
provider
.GetRequiredService<IConfiguration>()
.GetConnectionString(ConnectionStringName)
?? throw new InvalidOperationException($"ConnectionStrings:{ConnectionStringName} is not configured.");
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="dbup-postgresql" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
</ItemGroup>
</Project>
+4
View File
@@ -4,8 +4,12 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.6.4" />
<PackageVersion Include="dbup-postgresql" Version="7.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.13" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.10.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.10.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.20.1" />
+1
View File
@@ -11,6 +11,7 @@
</Folder>
<Folder Name="/Commons/">
<Project Path="Commons/Hearth.Commons/Hearth.Commons.csproj" />
<Project Path="Commons/Hearth.Database/Hearth.Database.csproj" />
</Folder>
<Folder Name="/Characters/">
<Project Path="Characters/Hearth.Characters/Hearth.Characters.csproj" />
+11 -1
View File
@@ -1,5 +1,15 @@
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.Hearth_Api>("api");
var postgres = builder
.AddPostgres("postgres")
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);
var database = postgres.AddDatabase("hearth");
builder
.AddProject<Projects.Hearth_Api>("api")
.WithReference(database, "Postgres")
.WaitFor(database);
builder.Build().Run();
@@ -6,6 +6,10 @@
<UserSecretsId>388fbdf7-df04-4c75-858e-1bdc720d2a91</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.PostgreSQL" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hearth.Api\Hearth.Api.csproj" />
</ItemGroup>