using System.CommandLine; using Femto.Database; using Femto.Database.Seed; using Npgsql; var nameArg = new Argument("name", "the name of the migration"); var migrationsDirectoryOption = new Option( ["--migrations-directory"], () => "./Migrations", "the directory where the migrations are stored" ); var newCommand = new Command("new", "creates a new migrations") { nameArg, migrationsDirectoryOption, }; newCommand.SetHandler(MakeNewMigration, nameArg, migrationsDirectoryOption); var connectionStringArg = new Argument( "--connection-string", "the connection string to the database" ); var upCommand = new Command("up", "update the database to the most current migration") { migrationsDirectoryOption, connectionStringArg, }; upCommand.SetHandler(MigrateUp, migrationsDirectoryOption, connectionStringArg); var seedCommand = new Command("seed", "seed the database with test data") { connectionStringArg }; seedCommand.SetHandler(Seed, connectionStringArg); // Add these near the top with other command definitions var yesOption = new Option( ["-y", "--yes"], "Skip confirmation prompt" ); var resetCommand = new Command("reset", "drops the existing database, runs migrations, and seeds test data") { connectionStringArg, migrationsDirectoryOption, yesOption }; resetCommand.SetHandler(CreateTestDatabase, connectionStringArg, migrationsDirectoryOption, yesOption); var rootCommand = new RootCommand("migrator") { newCommand, upCommand, seedCommand, resetCommand }; return await rootCommand.InvokeAsync(args); static async Task MakeNewMigration(string name, string migrationsDirectory) { Directory.CreateDirectory(migrationsDirectory); var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); var fileName = $"{timestamp}_{name}.sql"; var filePath = Path.Combine(migrationsDirectory, fileName); // Write an initial comment in the file File.WriteAllText(filePath, $"-- Migration: {name}\n-- Created at: {DateTime.UtcNow}"); // Notify the user Console.WriteLine($"Migration created successfully: {filePath}"); } static async Task MigrateUp(string migrationsDirectory, string connectionString) { if (!Directory.Exists(migrationsDirectory)) { Console.WriteLine("Migrations directory does not exist."); return; } await using var dataSource = NpgsqlDataSource.Create(connectionString); await using var migrator = new Migrator(migrationsDirectory, dataSource); await migrator.Migrate(); } static async Task Seed(string connectionString) { var dataSource = NpgsqlDataSource.Create(connectionString); await TestDataSeeder.Seed(dataSource); } static async Task CreateTestDatabase(string connectionString, string migrationsDirectory, bool skipConfirmation) { var builder = new NpgsqlConnectionStringBuilder(connectionString); if (!skipConfirmation) { builder.Database = "postgres"; try { await using var dataSource = NpgsqlDataSource.Create(builder.ConnectionString); await using var conn = await dataSource.OpenConnectionAsync(); await using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT 1 FROM pg_database WHERE datname = '{builder.Database}'"; var exists = await cmd.ExecuteScalarAsync(); if (exists is true) { Console.WriteLine("WARNING: This will drop the existing database and recreate it."); Console.Write("Are you sure you want to continue? (y/N): "); var response = Console.ReadLine()?.ToLower(); if (response != "y") { Console.WriteLine("Operation cancelled."); return; } } } catch (Exception ex) { Console.WriteLine($"Error checking database existence: {ex.Message}"); throw; } } var databaseName = builder.Database; builder.Database = "postgres"; // Connect to default database to drop the target try { // Connect to postgres database to drop/create the target database await using var dataSource = NpgsqlDataSource.Create(builder.ConnectionString); await using var conn = await dataSource.OpenConnectionAsync(); await using var cmd = conn.CreateCommand(); // Drop database if it exists cmd.CommandText = $"DROP DATABASE IF EXISTS {databaseName} WITH (FORCE)"; await cmd.ExecuteNonQueryAsync(); // Create fresh database cmd.CommandText = $"CREATE DATABASE {databaseName}"; await cmd.ExecuteNonQueryAsync(); // Now run migrations and seed with the original connection string Console.WriteLine("Running migrations..."); await MigrateUp(migrationsDirectory, connectionString); Console.WriteLine("Seeding database..."); await Seed(connectionString); Console.WriteLine("Database reset completed successfully."); } catch (Exception ex) { Console.WriteLine($"Error resetting database: {ex.Message}"); throw; } }