using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Npgsql;
namespace FinlyticCore.Database;
///
/// 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.
///
public static class DatabaseBootstrapper
{
///
/// 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.
///
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.");
}
}
///
/// Combines catalog database auto-creation and EF Core Migration execution in a single call.
///
public static async Task MigrateWithBootstrapAsync(
this TContext context,
string connectionString,
ILogger? logger = null,
CancellationToken cancellationToken = default) where TContext : DbContext
{
await EnsureDatabaseCreatedAsync(connectionString, logger, cancellationToken);
await context.Database.MigrateAsync(cancellationToken);
}
}