From 981a8b282567102072ca19b337e4d13606fb2369 Mon Sep 17 00:00:00 2001 From: manukminasyan Date: Fri, 31 Jan 2025 17:57:43 +0400 Subject: [PATCH] Implement upgrade command for Custom Fields plugin with database schema updates and data migration --- src/Commands/Upgrade/UpdateDatabaseSchema.php | 128 ++++++++++++++ src/Commands/Upgrade/UpdateExistingData.php | 69 ++++++++ src/Commands/UpgradeCommand.php | 63 +++++++ src/Commands/UpgradeCustomFieldsCommand.php | 156 ------------------ src/CustomFieldsServiceProvider.php | 4 +- 5 files changed, 262 insertions(+), 158 deletions(-) create mode 100644 src/Commands/Upgrade/UpdateDatabaseSchema.php create mode 100644 src/Commands/Upgrade/UpdateExistingData.php create mode 100644 src/Commands/UpgradeCommand.php delete mode 100644 src/Commands/UpgradeCustomFieldsCommand.php diff --git a/src/Commands/Upgrade/UpdateDatabaseSchema.php b/src/Commands/Upgrade/UpdateDatabaseSchema.php new file mode 100644 index 00000000..bea28daf --- /dev/null +++ b/src/Commands/Upgrade/UpdateDatabaseSchema.php @@ -0,0 +1,128 @@ +isDryRun(); + + $command->info('--- Updating database schema...'); + $command->newLine(); + + // Logic to update database schema + $this->createCustomFieldSectionsTable($command, $isDryRun); + $this->updateCustomFieldsTable($command, $isDryRun); + $this->removeDeletedAtColumns($command, $isDryRun); + + $command->newLine(); + $command->info('Database schema update step completed.'); + $command->newLine(); + + return $next($command); + } + + private function createCustomFieldSectionsTable(Command $command, bool $isDryRun): void + { + $sectionsTable = config('custom-fields.table_names.custom_field_sections', 'custom_field_sections'); + + if (!Schema::hasTable($sectionsTable)) { + if ($isDryRun) { + $command->line("Table `{$sectionsTable}` would be created."); + } else { + Schema::create($sectionsTable, function (Blueprint $table): void { + $table->id(); + if (Utils::isTenantEnabled()) { + $table->foreignId(config('custom-fields.column_names.tenant_foreign_key'))->nullable()->index(); + } + $table->string('code'); + $table->string('name'); + $table->string('type'); + $table->string('entity_type'); + $table->unsignedBigInteger('sort_order')->nullable(); + $table->string('description')->nullable(); + $table->boolean('active')->default(true); + $table->boolean('system_defined')->default(false); + + $uniqueColumns = ['entity_type', 'code']; + if (Utils::isTenantEnabled()) { + $uniqueColumns[] = config('custom-fields.column_names.tenant_foreign_key'); + } + $table->unique($uniqueColumns); + + $table->timestamps(); + }); + $command->info("Table `{$sectionsTable}` created successfully."); + } + } else { + $command->line("Table `{$sectionsTable}` already exists. Skipping creation."); + } + } + + private function updateCustomFieldsTable(Command $command, bool $isDryRun): void + { + $customFieldsTable = config('custom-fields.table_names.custom_fields'); + + $columnsToAdd = []; + if (!Schema::hasColumn($customFieldsTable, 'custom_field_section_id')) { + $columnsToAdd[] = 'custom_field_section_id'; + } + if (!Schema::hasColumn($customFieldsTable, 'width')) { + $columnsToAdd[] = 'width'; + } + + if (!empty($columnsToAdd)) { + if ($isDryRun) { + foreach ($columnsToAdd as $column) { + $command->line("Column `{$column}` would be added to `{$customFieldsTable}` table."); + } + } else { + Schema::table($customFieldsTable, function (Blueprint $table) use ($columnsToAdd): void { + if (in_array('custom_field_section_id', $columnsToAdd)) { + $table->unsignedBigInteger('custom_field_section_id')->nullable()->after('id'); + } + if (in_array('width', $columnsToAdd)) { + $table->string('width')->nullable()->after('custom_field_section_id'); + } + }); + foreach ($columnsToAdd as $column) { + $command->info("Added `{$column}` column to `{$customFieldsTable}` table."); + } + } + } else { + $command->line("Columns `custom_field_section_id` and `width` already exist in `{$customFieldsTable}`. Skipping."); + } + } + + private function removeDeletedAtColumns(Command $command, bool $isDryRun): void + { + $tablesWithDeletedAt = [ + config('custom-fields.table_names.custom_fields'), + config('custom-fields.table_names.custom_field_options'), + config('custom-fields.table_names.custom_field_values'), + ]; + + foreach ($tablesWithDeletedAt as $table) { + if (Schema::hasColumn($table, 'deleted_at')) { + if ($isDryRun) { + $command->line("Column `deleted_at` would be removed from `{$table}` table."); + } else { + Schema::table($table, function (Blueprint $table): void { + $table->dropSoftDeletes(); + }); + $command->info("Removed `deleted_at` column from `{$table}` table."); + } + } else { + $command->line("Column `deleted_at` does not exist in `{$table}`. Skipping."); + } + } + } +} diff --git a/src/Commands/Upgrade/UpdateExistingData.php b/src/Commands/Upgrade/UpdateExistingData.php new file mode 100644 index 00000000..8dfad657 --- /dev/null +++ b/src/Commands/Upgrade/UpdateExistingData.php @@ -0,0 +1,69 @@ +isDryRun(); + + $command->info('--- Updating existing data...'); + $command->newLine(); + + DB::transaction(function () use ($command, $isDryRun): void { + $entityTypes = CustomField::query() + ->whereNull('custom_field_section_id') + ->pluck('entity_type'); + + if ($entityTypes->isEmpty()) { + $command->info('No custom fields found that require updating.'); + return; + } + + $command->info('Updating custom fields for the following entity types:'); + $command->line($entityTypes->implode(', ')); + $command->newLine(); + + $progressBar = $command->output->createProgressBar($entityTypes->count()); + $progressBar->start(); + + foreach ($entityTypes as $entityType) { + if ($isDryRun) { + $command->line("A new section would be created for entity type `{$entityType}`."); + } else { + $section = CustomFieldSection::create([ + 'entity_type' => $entityType, + 'name' => __('custom-fields::custom-fields.section.default.new_section'), + 'code' => 'new_section', + 'type' => CustomFieldSectionType::HEADLESS, + ]); + + CustomField::whereNull('custom_field_section_id') + ->where('entity_type', $entityType) + ->update([ + 'custom_field_section_id' => $section->id, + 'width' => CustomFieldWidth::_100, + ]); + + $command->line("Custom fields for entity type `{$entityType}` have been updated."); + } + $progressBar->advance(); + } + + $progressBar->finish(); + $command->newLine(2); + $command->info('Existing data update step completed.'); + }); + + return $next($command); + } +} diff --git a/src/Commands/UpgradeCommand.php b/src/Commands/UpgradeCommand.php new file mode 100644 index 00000000..9af141b7 --- /dev/null +++ b/src/Commands/UpgradeCommand.php @@ -0,0 +1,63 @@ +info('Welcome to the Custom Fields Upgrade Command!'); + $this->info('This command will upgrade the Custom Fields Filament Plugin to version 1.0.'); + $this->newLine(); + + if ($this->isDryRun()) { + $this->warn('Running in Dry Run mode. No changes will be made.'); + } + + if (!$this->confirm('Do you wish to continue?', true)) { + $this->info('Upgrade cancelled by the user.'); + return self::SUCCESS; + } + + $this->newLine(); + + try { + app(Pipeline::class) + ->send($this) + ->through([ + UpdateDatabaseSchema::class, + UpdateExistingData::class, + ]) + ->thenReturn(); + + $this->info('Upgrade completed successfully.'); + return self::SUCCESS; + } catch (Throwable $e) { + $this->error('An error occurred during the upgrade process:'); + $this->error($e->getMessage()); + + \Log::error('Custom Fields Upgrade Error:', [ + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return self::FAILURE; + } + } + + public function isDryRun(): bool + { + return $this->option('dry-run'); + } +} diff --git a/src/Commands/UpgradeCustomFieldsCommand.php b/src/Commands/UpgradeCustomFieldsCommand.php deleted file mode 100644 index c4d92ba4..00000000 --- a/src/Commands/UpgradeCustomFieldsCommand.php +++ /dev/null @@ -1,156 +0,0 @@ -info('Starting the upgrade of Custom Fields Filament Plugin to version 1.0...'); - - // 1. Update Database Schema - $this->updateDatabaseSchema(); - - // 2. Update Existing Data - $this->updateExistingData(); - - $this->info('Upgrade completed successfully.'); - } - - /** - * Update the database schema without using migrations. - */ - private function updateDatabaseSchema(): void - { - $this->info('--- Updating database schema...'); - - // Create 'custom_field_sections' table if it doesn't exist - if (!Schema::hasTable('custom_field_sections')) { - Schema::create(config('custom-fields.table_names.custom_field_sections'), function (Blueprint $table): void { - $uniqueColumns = ['entity_type', 'code']; - - $table->id(); - - if (Utils::isTenantEnabled()) { - $table->foreignId(config('custom-fields.column_names.tenant_foreign_key'))->nullable()->index(); - $uniqueColumns[] = config('custom-fields.column_names.tenant_foreign_key'); - } - - $table->string('code'); - $table->string('name'); - $table->string('type'); - $table->string('entity_type'); - $table->unsignedBigInteger('sort_order')->nullable(); - - $table->string('description')->nullable(); - - $table->boolean('active')->default(true); - $table->boolean('system_defined')->default(false); - - $table->unique($uniqueColumns); - - $table->timestamps(); - }); - - $this->info('Table `custom_field_sections` created successfully.'); - } - - // Add 'custom_field_section_id' and 'width' columns to 'custom_fields' table if they don't exist - Schema::table(config('custom-fields.table_names.custom_fields'), function (Blueprint $table) { - if (!Schema::hasColumn('custom_fields', 'custom_field_section_id')) { - $table->unsignedBigInteger('custom_field_section_id')->nullable()->after('id'); - $this->info('Added `custom_field_section_id` column to `custom_fields` table.'); - } - - if (!Schema::hasColumn('custom_fields', 'width')) { - $table->string('width')->nullable()->after('custom_field_section_id'); - $this->info('Added `width` column to `custom_fields` table.'); - } - - $table->dropSoftDeletes(); - - $this->info('Removed `deleted_at` column from `custom_fields` table.'); - }); - - // Remove 'deleted_at' column from 'custom_field_options' table if it exists - Schema::table(config('custom-fields.table_names.custom_field_options'), function (Blueprint $table) { - if (Schema::hasColumn('custom_fields', 'deleted_at')) { - $table->dropSoftDeletes(); - - $this->info('Removed `deleted_at` column from `custom_field_options` table.'); - } - }); - - // Remove 'deleted_at' column from 'custom_field_values' table if it exists - Schema::create(config('custom-fields.table_names.custom_field_values'), function (Blueprint $table): void { - if (Schema::hasColumn('custom_fields', 'deleted_at')) { - $table->dropSoftDeletes(); - - $this->info('Removed `deleted_at` column from `custom_field_values` table.'); - } - }); - - - $this->info('Database schema updated successfully.'); - } - - /** - * Update existing data to match the new schema. - */ - private function updateExistingData(): void - { - $this->info('--- Updating existing data...'); - - // Use a transaction to ensure data integrity - DB::transaction(function () { - CustomField::query() - ->whereNull('custom_field_section_id') - ->orderBy('sort_order') - ->get() - ->groupBy('entity_type') - ->each(function ($customFields, $entityType) { - // Create a new section for each entity type - $section = CustomFieldSection::create([ - 'entity_type' => $entityType, - 'name' => __('custom-fields::custom-fields.section.default.new_section'), - 'code' => 'new_section', - 'type' => CustomFieldSectionType::HEADLESS, - ]); - - // Update each custom field to set the 'custom_field_section_id' - $customFields->each(fn($customField) => $customField->update([ - 'custom_field_section_id' => $section->id, - 'width' => CustomFieldWidth::_100 - ])); - }); - }); - - $this->info('Existing data updated successfully.'); - } -} diff --git a/src/CustomFieldsServiceProvider.php b/src/CustomFieldsServiceProvider.php index 5020cadd..1d2c7c9e 100644 --- a/src/CustomFieldsServiceProvider.php +++ b/src/CustomFieldsServiceProvider.php @@ -14,7 +14,7 @@ use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; use Relaticle\CustomFields\Commands\FilamentCustomFieldCommand; -use Relaticle\CustomFields\Commands\UpgradeCustomFieldsCommand; +use Relaticle\CustomFields\Commands\UpgradeCommand; use Relaticle\CustomFields\Contracts\CustomsFieldsMigrators; use Relaticle\CustomFields\Livewire\ManageCustomField; use Relaticle\CustomFields\Livewire\ManageCustomFieldSection; @@ -155,7 +155,7 @@ protected function getCommands(): array { return [ FilamentCustomFieldCommand::class, - UpgradeCustomFieldsCommand::class, + UpgradeCommand::class, ]; }