feat(core): add shared DTOs, MqttTopics constants, DatabaseBootstrapper, and ManagedMqttClient extensions
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
|
||||
namespace FinlyticCore.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Utility for auto-bootstrapping PostgreSQL databases in a multi-service architecture.
|
||||
/// Ensures the target catalog database exists prior to EF Core connection and migration execution.
|
||||
/// </summary>
|
||||
public static class DatabaseBootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the target PostgreSQL database exists. If not, connects to the default administrative
|
||||
/// database ('postgres') and executes CREATE DATABASE so that EF Core migrations can succeed.
|
||||
/// </summary>
|
||||
public static async Task EnsureDatabaseCreatedAsync(
|
||||
string connectionString,
|
||||
ILogger? logger = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var builder = new NpgsqlConnectionStringBuilder(connectionString);
|
||||
string targetDb = builder.Database ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(targetDb) || string.Equals(targetDb, "postgres", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporarily connect to the default 'postgres' database to query pg_database
|
||||
builder.Database = "postgres";
|
||||
string adminConnStr = builder.ConnectionString;
|
||||
|
||||
await using var conn = new NpgsqlConnection(adminConnStr);
|
||||
await conn.OpenAsync(cancellationToken);
|
||||
|
||||
await using var checkCmd = new NpgsqlCommand(
|
||||
"SELECT 1 FROM pg_database WHERE datname = @dbname;", conn);
|
||||
checkCmd.Parameters.AddWithValue("dbname", targetDb);
|
||||
var exists = await checkCmd.ExecuteScalarAsync(cancellationToken);
|
||||
|
||||
if (exists == null || exists == DBNull.Value)
|
||||
{
|
||||
logger?.LogInformation("[DatabaseBootstrapper] Database '{TargetDb}' does not exist on PostgreSQL host. Creating it automatically...", targetDb);
|
||||
|
||||
// CREATE DATABASE cannot be executed as a parameterized identifier
|
||||
await using var createCmd = new NpgsqlCommand(
|
||||
$"CREATE DATABASE \"{targetDb.Replace("\"", "\"\"")}\";", conn);
|
||||
await createCmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
|
||||
logger?.LogInformation("[DatabaseBootstrapper] Successfully created database '{TargetDb}'.", targetDb);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning(ex, "[DatabaseBootstrapper] Auto-creation check failed or skipped for connection. Continuing with migration.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines catalog database auto-creation and EF Core Migration execution in a single call.
|
||||
/// </summary>
|
||||
public static async Task MigrateWithBootstrapAsync<TContext>(
|
||||
this TContext context,
|
||||
string connectionString,
|
||||
ILogger? logger = null,
|
||||
CancellationToken cancellationToken = default) where TContext : DbContext
|
||||
{
|
||||
await EnsureDatabaseCreatedAsync(connectionString, logger, cancellationToken);
|
||||
await context.Database.MigrateAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user