diff --git a/app/Console/Commands/ConversionStatusCommand.php b/app/Console/Commands/ConversionStatusCommand.php new file mode 100644 index 000000000..9c555cbf3 --- /dev/null +++ b/app/Console/Commands/ConversionStatusCommand.php @@ -0,0 +1,106 @@ +ensureConversionsTableExists(); + + $this->showConversionStatus(); + + return 0; + } + + /** + * Ensure that the conversions table exists. + * + * @return void + */ + protected function ensureConversionsTableExists() + { + if (!DB::connection()->getSchemaBuilder()->hasTable('conversions')) { + DB::connection()->getSchemaBuilder()->create('conversions', function ($table) { + $table->increments('id'); + $table->string('conversion')->unique(); + $table->integer('batch'); + $table->timestamps(); + }); + $this->info('Created conversions table.'); + } + } + + /** + * Display the status of each conversion file. + * + * @return void + */ + protected function showConversionStatus() + { + // Retrieve all executed conversions with their batch and timestamps + $executedConversions = DB::table('conversions')->get(); + + // Create a map of executed conversions for quick lookup + $executedConversionsMap = $executedConversions->keyBy('conversion'); + + $files = File::glob(database_path('conversions') . '/*.php'); + + // Sort files for consistent display + sort($files); + + $statusData = []; + + foreach ($files as $file) { + $fileName = basename($file, '.php'); + + if ($executedConversionsMap->has($fileName)) { + $status = 'Yes'; + $batchNumber = $executedConversionsMap->get($fileName)->batch; + $executedAt = $executedConversionsMap->get($fileName)->created_at; + } else { + $status = 'No'; + $batchNumber = 'N/A'; + $executedAt = 'N/A'; + } + + $statusData[] = [ + 'Ran?' => $status, + 'Conversion' => $fileName, + 'Batch' => $batchNumber, + 'Executed At' => $executedAt, + ]; + } + + if (empty($statusData)) { + $this->info('No conversion files found.'); + return; + } + + // Display the table with the additional columns + $this->table(['Ran?', 'Conversion', 'Batch', 'Executed At'], $statusData); + } +} diff --git a/app/Console/Commands/MakeConversionCommand.php b/app/Console/Commands/MakeConversionCommand.php new file mode 100644 index 000000000..2f98682c5 --- /dev/null +++ b/app/Console/Commands/MakeConversionCommand.php @@ -0,0 +1,169 @@ +files = $files; + $this->composer = $composer; + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + // Get the name of the conversion + $name = trim($this->argument('name')); + + // Generate the class name + $className = $this->getClassName($name); + + // Generate the file path + $filePath = $this->getFilePath($name); + + // Build the class content + $classContent = $this->buildClass($className); + + // Create the conversion file + if ($this->files->exists($filePath)) { + $this->error("Conversion {$className} already exists!"); + return 1; + } + + $this->makeDirectory($filePath); + + $this->files->put($filePath, $classContent); + + $this->info("Created Conversion: " . basename($filePath)); + + // Dump the autoloads + $this->composer->dumpAutoloads(); + + return 0; + } + + /** + * Get the class name from the conversion name. + * + * @param string $name + * @return string + */ + protected function getClassName($name) + { + return Str::studly($name); + } + + /** + * Get the file path for the conversion. + * + * @param string $name + * @return string + */ + protected function getFilePath($name) + { + $timestamp = date('Y_m_d_His'); + $fileName = $timestamp . '_' . $name . '.php'; + return database_path('conversions') . '/' . $fileName; + } + + /** + * Build the class content. + * + * @param string $className + * @return string + */ + protected function buildClass($className) + { + return <<files->isDirectory($directory)) { + $this->files->makeDirectory($directory, 0755, true, true); + } + } +} diff --git a/app/Console/Commands/RunConversionsCommand.php b/app/Console/Commands/RunConversionsCommand.php new file mode 100644 index 000000000..3839c4bbb --- /dev/null +++ b/app/Console/Commands/RunConversionsCommand.php @@ -0,0 +1,183 @@ +ensureConversionsTableExists(); + + if ($this->option('rollback')) { + $steps = (int) $this->option('step'); + $this->rollbackConversions($steps); + } else { + $this->runConversions(); + } + + return 0; + } + + /** + * Ensure that the conversions table exists. + * + * @return void + */ + protected function ensureConversionsTableExists() + { + if (!DB::connection()->getSchemaBuilder()->hasTable('conversions')) { + DB::connection()->getSchemaBuilder()->create('conversions', function ($table) { + $table->increments('id'); + $table->string('conversion'); + $table->integer('batch'); + $table->timestamps(); + }); + $this->info('Created conversions table.'); + } + } + + /** + * Run all pending conversions. + * + * @return void + */ + protected function runConversions() + { + $executed = DB::table('conversions')->pluck('conversion')->toArray(); + $lastBatch = DB::table('conversions')->max('batch'); + $batch = $lastBatch ? $lastBatch + 1 : 1; + + $files = File::glob(database_path('conversions') . '/*.php'); + + // Sort files to ensure they run in order + sort($files); + + foreach ($files as $file) { + $fileName = basename($file, '.php'); + + if (!in_array($fileName, $executed)) { + $this->line("Running: {$fileName}"); + + try { + $instance = include $file; + + if (!is_object($instance)) { + $this->error("The file {$fileName}.php did not return a class instance."); + continue; + } + + if (!method_exists($instance, 'up')) { + $this->error("Method 'up' does not exist in the conversion returned by {$fileName}.php"); + continue; + } + + $instance->up(); + + // If no exception occurred, consider it successful + DB::table('conversions')->insert([ + 'conversion' => $fileName, + 'batch' => $batch, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $this->info("Completed: {$fileName}"); + } catch (\Exception $e) { + $this->error("Failed to run {$fileName}: {$e->getMessage()}"); + } + } + } + } + + /** + * Rollback the specified number of batches. + * + * @param int $steps + * @return void + */ + protected function rollbackConversions($steps) + { + $lastBatch = DB::table('conversions')->max('batch'); + + if ($lastBatch === null) { + $this->info('No conversions to rollback.'); + return; + } + + $batches = DB::table('conversions') + ->select('batch') + ->distinct() + ->orderBy('batch', 'desc') + ->limit($steps) + ->pluck('batch') + ->toArray(); + + if (empty($batches)) { + $this->info('No conversions to rollback.'); + return; + } + + foreach ($batches as $batch) { + $conversions = DB::table('conversions') + ->where('batch', $batch) + ->orderBy('id', 'desc') + ->get(); + + foreach ($conversions as $conversion) { + $fileName = $conversion->conversion; + $file = database_path('conversions') . '/' . $fileName . '.php'; + + if (File::exists($file)) { + $this->line("Rolling back: {$fileName}"); + + try { + $instance = include $file; + + if (!is_object($instance)) { + $this->error("The file {$fileName}.php did not return a class instance."); + continue; + } + + if (!method_exists($instance, 'down')) { + $this->error("Method 'down' does not exist in the conversion returned by {$fileName}.php"); + continue; + } + + $instance->down(); + + DB::table('conversions')->where('id', $conversion->id)->delete(); + + $this->info("Rolled back: {$fileName}"); + } catch (\Exception $e) { + $this->error("Failed to rollback {$fileName}: {$e->getMessage()}"); + } + } else { + $this->error("Conversion file {$fileName}.php does not exist."); + } + } + } + } +} diff --git a/composer.json b/composer.json index 93bc6fdfa..814376e9f 100644 --- a/composer.json +++ b/composer.json @@ -70,7 +70,8 @@ "Database\\Seeders\\": "database/seeders/", "Database\\Seeders\\Omop\\": "database/seeders/omop", "Database\\Migrations\\": "database/migrations/", - "Database\\Beta\\": "database/beta/" + "Database\\Beta\\": "database/beta/", + "Database\\Conversions\\": "database/conversions/" } }, "autoload-dev": { diff --git a/database/conversions/2024_11_08_135915_example.php b/database/conversions/2024_11_08_135915_example.php new file mode 100644 index 000000000..0198a9620 --- /dev/null +++ b/database/conversions/2024_11_08_135915_example.php @@ -0,0 +1,25 @@ +id(); + $table->string('conversion')->unique(); + $table->integer('batch'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('conversions'); + } +};