From 69de0ea27c536901f43bfa743421f8cdbb1ab3e5 Mon Sep 17 00:00:00 2001 From: IanM Date: Sat, 15 Aug 2026 08:56:17 +0100 Subject: [PATCH 1/2] fix: restore 1.x migration filenames and make them idempotent Two migrations were renamed during the 2.0 refactor: 2026_04_17_000000_alter_polls_add_draft_columns.php -> 2026_05_18_000000_alter_polls_add_draft_columns.php 2023_07_14_000002_delete_extra_polls_columns.php.disabled -> 2026_04_15_000000_delete_extra_polls_columns.php Flarum tracks applied migrations by filename, so sites upgrading from 1.x had no record of the new names and re-ran migrations whose changes were already applied, failing with: SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'published_at' Restore both files to their 1.x filenames so previously-applied migrations are correctly skipped, and guard every step (column adds, drops, and the index) with hasColumn()/hasIndex() checks in both directions. The guards cover installs that already ran the migrations under the 2.x filenames, and repair partially-applied states. delete_extra_polls_columns no longer uses Migration::dropColumns(), whose generated up/down are unguarded; it is now written out longhand with the same column definitions preserved for rollback. --- ...7_14_000002_delete_extra_polls_columns.php | 46 +++++++++++++ ...4_15_000000_delete_extra_polls_columns.php | 18 ----- ...7_000000_alter_polls_add_draft_columns.php | 67 +++++++++++++++++++ ...8_000000_alter_polls_add_draft_columns.php | 35 ---------- 4 files changed, 113 insertions(+), 53 deletions(-) create mode 100644 migrations/2023_07_14_000002_delete_extra_polls_columns.php delete mode 100644 migrations/2026_04_15_000000_delete_extra_polls_columns.php create mode 100644 migrations/2026_04_17_000000_alter_polls_add_draft_columns.php delete mode 100644 migrations/2026_05_18_000000_alter_polls_add_draft_columns.php diff --git a/migrations/2023_07_14_000002_delete_extra_polls_columns.php b/migrations/2023_07_14_000002_delete_extra_polls_columns.php new file mode 100644 index 00000000..882b3294 --- /dev/null +++ b/migrations/2023_07_14_000002_delete_extra_polls_columns.php @@ -0,0 +1,46 @@ + fn (Blueprint $table) => $table->boolean('public_poll')->default(false), + 'allow_multiple_votes' => fn (Blueprint $table) => $table->boolean('allow_multiple_votes')->default(false), + 'max_votes' => fn (Blueprint $table) => $table->integer('max_votes')->unsigned()->default(0), +]; + +return [ + 'up' => function (Builder $schema) use ($columns) { + $existing = array_filter( + array_keys($columns), + fn (string $column) => $schema->hasColumn('polls', $column) + ); + + if ($existing !== []) { + $schema->table('polls', function (Blueprint $table) use ($existing) { + $table->dropColumn(array_values($existing)); + }); + } + }, + 'down' => function (Builder $schema) use ($columns) { + foreach ($columns as $name => $definition) { + if (! $schema->hasColumn('polls', $name)) { + $schema->table('polls', function (Blueprint $table) use ($definition) { + $definition($table); + }); + } + } + }, +]; diff --git a/migrations/2026_04_15_000000_delete_extra_polls_columns.php b/migrations/2026_04_15_000000_delete_extra_polls_columns.php deleted file mode 100644 index 7a4d0959..00000000 --- a/migrations/2026_04_15_000000_delete_extra_polls_columns.php +++ /dev/null @@ -1,18 +0,0 @@ - ['boolean', 'default' => false], - 'allow_multiple_votes' => ['boolean', 'default' => false], - 'max_votes' => ['integer', 'unsigned' => true, 'default' => 0], -]); diff --git a/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php b/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php new file mode 100644 index 00000000..762d05b7 --- /dev/null +++ b/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php @@ -0,0 +1,67 @@ + function (Builder $schema) { + // This migration was released on 1.x under this same filename, so sites + // upgrading from 1.x may already have these columns. Each step is + // guarded to keep the migration safe to re-run against any state. + $columns = [ + 'published_at' => fn (Blueprint $table, string $after) => $table->timestamp('published_at')->nullable()->after($after), + 'scheduled_publish_at' => fn (Blueprint $table, string $after) => $table->timestamp('scheduled_publish_at')->nullable()->after($after), + 'scheduled_publish_error' => fn (Blueprint $table, string $after) => $table->text('scheduled_publish_error')->nullable()->after($after), + ]; + + $after = 'end_date'; + + foreach ($columns as $name => $definition) { + if (! $schema->hasColumn('polls', $name)) { + $schema->table('polls', function (Blueprint $table) use ($definition, $after) { + $definition($table, $after); + }); + } + + $after = $name; + } + + if (! $schema->hasIndex('polls', 'polls_scheduled_publish_at_index')) { + $schema->table('polls', function (Blueprint $table) { + $table->index('scheduled_publish_at', 'polls_scheduled_publish_at_index'); + }); + } + + // Backfill: existing rows are considered published. + $schema->getConnection()->table('polls') + ->whereNull('published_at') + ->update(['published_at' => $schema->getConnection()->raw('created_at')]); + }, + 'down' => function (Builder $schema) { + if ($schema->hasIndex('polls', 'polls_scheduled_publish_at_index')) { + $schema->table('polls', function (Blueprint $table) { + $table->dropIndex('polls_scheduled_publish_at_index'); + }); + } + + $existing = array_filter( + ['published_at', 'scheduled_publish_at', 'scheduled_publish_error'], + fn (string $column) => $schema->hasColumn('polls', $column) + ); + + if ($existing !== []) { + $schema->table('polls', function (Blueprint $table) use ($existing) { + $table->dropColumn(array_values($existing)); + }); + } + }, +]; diff --git a/migrations/2026_05_18_000000_alter_polls_add_draft_columns.php b/migrations/2026_05_18_000000_alter_polls_add_draft_columns.php deleted file mode 100644 index ca49d649..00000000 --- a/migrations/2026_05_18_000000_alter_polls_add_draft_columns.php +++ /dev/null @@ -1,35 +0,0 @@ - function (Builder $schema) { - $schema->table('polls', function (Blueprint $table) { - $table->timestamp('published_at')->nullable()->after('end_date'); - $table->timestamp('scheduled_publish_at')->nullable()->after('published_at'); - $table->text('scheduled_publish_error')->nullable()->after('scheduled_publish_at'); - $table->index('scheduled_publish_at', 'polls_scheduled_publish_at_index'); - }); - - // Backfill: existing rows are considered published. - $schema->getConnection()->table('polls') - ->whereNull('published_at') - ->update(['published_at' => $schema->getConnection()->raw('created_at')]); - }, - 'down' => function (Builder $schema) { - $schema->table('polls', function (Blueprint $table) { - $table->dropIndex('polls_scheduled_publish_at_index'); - $table->dropColumn(['published_at', 'scheduled_publish_at', 'scheduled_publish_error']); - }); - }, -]; From 212c3d06b15c69d2eb822d9704ce2ae587aa1514 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 15 Aug 2026 07:57:36 +0000 Subject: [PATCH 2/2] Apply fixes from StyleCI --- migrations/2023_07_14_000002_delete_extra_polls_columns.php | 2 +- .../2026_04_17_000000_alter_polls_add_draft_columns.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/migrations/2023_07_14_000002_delete_extra_polls_columns.php b/migrations/2023_07_14_000002_delete_extra_polls_columns.php index 882b3294..20d96511 100644 --- a/migrations/2023_07_14_000002_delete_extra_polls_columns.php +++ b/migrations/2023_07_14_000002_delete_extra_polls_columns.php @@ -36,7 +36,7 @@ }, 'down' => function (Builder $schema) use ($columns) { foreach ($columns as $name => $definition) { - if (! $schema->hasColumn('polls', $name)) { + if (!$schema->hasColumn('polls', $name)) { $schema->table('polls', function (Blueprint $table) use ($definition) { $definition($table); }); diff --git a/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php b/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php index 762d05b7..36cf958b 100644 --- a/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php +++ b/migrations/2026_04_17_000000_alter_polls_add_draft_columns.php @@ -26,7 +26,7 @@ $after = 'end_date'; foreach ($columns as $name => $definition) { - if (! $schema->hasColumn('polls', $name)) { + if (!$schema->hasColumn('polls', $name)) { $schema->table('polls', function (Blueprint $table) use ($definition, $after) { $definition($table, $after); }); @@ -35,7 +35,7 @@ $after = $name; } - if (! $schema->hasIndex('polls', 'polls_scheduled_publish_at_index')) { + if (!$schema->hasIndex('polls', 'polls_scheduled_publish_at_index')) { $schema->table('polls', function (Blueprint $table) { $table->index('scheduled_publish_at', 'polls_scheduled_publish_at_index'); });