Fix invitations error: Closes #1833

This commit is contained in:
DariusIII
2026-01-15 21:24:05 +01:00
parent 0d40bfefe6
commit f45e72949b
2 changed files with 230 additions and 0 deletions
@@ -0,0 +1,190 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* This migration fixes the invitations table by:
* 1. Dropping the old FK_users_inv foreign key constraint
* 2. Renaming users_id to invited_by if needed
* 3. Adding missing columns for the new invitation system
*/
public function up(): void
{
// First, drop the old foreign key constraint if it exists
$this->dropForeignKeySafely('invitations', 'FK_users_inv');
Schema::table('invitations', function (Blueprint $table) {
// Rename users_id to invited_by if needed
if (Schema::hasColumn('invitations', 'users_id') && ! Schema::hasColumn('invitations', 'invited_by')) {
$table->renameColumn('users_id', 'invited_by');
}
});
// Now add missing columns in a separate Schema call (after rename)
Schema::table('invitations', function (Blueprint $table) {
// Drop the old guid column if it exists (replaced by token)
if (Schema::hasColumn('invitations', 'guid') && Schema::hasColumn('invitations', 'token')) {
$table->dropColumn('guid');
}
// Add token if it doesn't exist
if (! Schema::hasColumn('invitations', 'token')) {
$table->string('token', 64)->unique()->after('id');
}
// Add email if it doesn't exist
if (! Schema::hasColumn('invitations', 'email')) {
$table->string('email')->after('token');
}
// Add expires_at if it doesn't exist
if (! Schema::hasColumn('invitations', 'expires_at')) {
if (Schema::hasColumn('invitations', 'invited_by')) {
$table->timestamp('expires_at')->after('invited_by');
} else {
$table->timestamp('expires_at')->nullable();
}
}
// Add used_at if it doesn't exist
if (! Schema::hasColumn('invitations', 'used_at')) {
$table->timestamp('used_at')->nullable();
}
// Add used_by if it doesn't exist
if (! Schema::hasColumn('invitations', 'used_by')) {
$table->unsignedBigInteger('used_by')->nullable();
}
// Add is_active if it doesn't exist
if (! Schema::hasColumn('invitations', 'is_active')) {
$table->boolean('is_active')->default(true);
}
// Add metadata if it doesn't exist
if (! Schema::hasColumn('invitations', 'metadata')) {
$table->json('metadata')->nullable();
}
});
// Migrate data from guid to token if guid still exists and has data
if (Schema::hasColumn('invitations', 'guid') && Schema::hasColumn('invitations', 'token')) {
DB::table('invitations')
->whereNull('token')
->orWhere('token', '')
->update(['token' => DB::raw('guid')]);
// Now drop guid
Schema::table('invitations', function (Blueprint $table) {
$table->dropColumn('guid');
});
}
// Add indexes safely
$this->addIndexSafely('invitations', ['invited_by'], 'invitations_invited_by_index');
$this->addIndexSafely('invitations', ['used_by'], 'invitations_used_by_index');
$this->addIndexSafely('invitations', ['token', 'is_active'], 'invitations_token_is_active_index');
$this->addIndexSafely('invitations', ['email', 'is_active'], 'invitations_email_is_active_index');
$this->addIndexSafely('invitations', ['expires_at'], 'invitations_expires_at_index');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Drop indexes first
$this->dropIndexSafely('invitations', 'invitations_invited_by_index');
$this->dropIndexSafely('invitations', 'invitations_used_by_index');
$this->dropIndexSafely('invitations', 'invitations_token_is_active_index');
$this->dropIndexSafely('invitations', 'invitations_email_is_active_index');
$this->dropIndexSafely('invitations', 'invitations_expires_at_index');
Schema::table('invitations', function (Blueprint $table) {
// Rename back to original column name
if (Schema::hasColumn('invitations', 'invited_by') && ! Schema::hasColumn('invitations', 'users_id')) {
$table->renameColumn('invited_by', 'users_id');
}
});
// Re-add the foreign key constraint
Schema::table('invitations', function (Blueprint $table) {
if (Schema::hasColumn('invitations', 'users_id')) {
$table->foreign('users_id', 'FK_users_inv')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
}
});
}
/**
* Safely drop a foreign key if it exists
*/
private function dropForeignKeySafely(string $table, string $foreignKeyName): void
{
try {
$foreignKeys = DB::select(
"SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
AND TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND CONSTRAINT_NAME = ?",
[$table, $foreignKeyName]
);
if (! empty($foreignKeys)) {
Schema::table($table, function (Blueprint $table) use ($foreignKeyName) {
$table->dropForeign($foreignKeyName);
});
}
} catch (\Exception $e) {
// Foreign key might not exist, continue
report($e);
}
}
/**
* Safely add an index if it doesn't exist
*/
private function addIndexSafely(string $table, array $columns, string $indexName): void
{
try {
$indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
if (empty($indexes)) {
Schema::table($table, function (Blueprint $table) use ($columns, $indexName) {
$table->index($columns, $indexName);
});
}
} catch (\Exception $e) {
// Index creation failed, but continue
report($e);
}
}
/**
* Safely drop an index if it exists
*/
private function dropIndexSafely(string $table, string $indexName): void
{
try {
$indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
if (! empty($indexes)) {
Schema::table($table, function (Blueprint $table) use ($indexName) {
$table->dropIndex($indexName);
});
}
} catch (\Exception $e) {
// Index drop failed, but continue
report($e);
}
}
};
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* Remove the legacy users_id column since we now use invited_by
*/
public function up(): void
{
Schema::table('invitations', function (Blueprint $table) {
// Drop the old users_id column if it exists and invited_by exists
if (Schema::hasColumn('invitations', 'users_id') && Schema::hasColumn('invitations', 'invited_by')) {
$table->dropColumn('users_id');
}
// Drop guid column if it still exists (we use token now)
if (Schema::hasColumn('invitations', 'guid')) {
$table->dropColumn('guid');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('invitations', function (Blueprint $table) {
if (! Schema::hasColumn('invitations', 'users_id')) {
$table->unsignedInteger('users_id')->nullable()->after('id');
}
});
}
};