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); } }