fix: add FK constraint on usage_alert_history.feature_code #56

Open
Charon wants to merge 3 commits from feat/constrain-feature-code-fk into dev
3 changed files with 144 additions and 0 deletions

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Add foreign key constraints on feature_code columns to prevent orphaned records.
*
* Fixes #12: feature_code in usage_alert_history not constrained to entitlement_features.
*/
public function up(): void
{
Schema::table('entitlement_usage_alert_history', function (Blueprint $table) {
$table->foreign('feature_code', 'usage_alert_feature_code_fk')
->references('code')
->on('entitlement_features')
->cascadeOnUpdate()
->restrictOnDelete();
});
Schema::table('entitlement_boosts', function (Blueprint $table) {
$table->foreign('feature_code', 'boosts_feature_code_fk')
->references('code')
->on('entitlement_features')
->cascadeOnUpdate()
->restrictOnDelete();
});
Schema::table('entitlement_usage_records', function (Blueprint $table) {
$table->foreign('feature_code', 'usage_records_feature_code_fk')
->references('code')
->on('entitlement_features')
->cascadeOnUpdate()
->restrictOnDelete();
});
}
public function down(): void
{
Schema::table('entitlement_usage_alert_history', function (Blueprint $table) {
$table->dropForeign('usage_alert_feature_code_fk');
});
Schema::table('entitlement_boosts', function (Blueprint $table) {
$table->dropForeign('boosts_feature_code_fk');
});
Schema::table('entitlement_usage_records', function (Blueprint $table) {
$table->dropForeign('usage_records_feature_code_fk');
});
}
};

View file

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Change namespaces.workspace_id FK from nullOnDelete to cascadeOnDelete.
*
* Fixes #10 — namespaces were orphaned with null workspace_id when
* the parent workspace was deleted.
*/
public function up(): void
{
Schema::table('namespaces', function (Blueprint $table) {
$table->dropForeign(['workspace_id']);
$table->foreign('workspace_id')
->references('id')
->on('workspaces')
->cascadeOnDelete();
});
}
public function down(): void
{
Schema::table('namespaces', function (Blueprint $table) {
$table->dropForeign(['workspace_id']);
$table->foreign('workspace_id')
->references('id')
->on('workspaces')
->nullOnDelete();
});
}
};

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Change parent_feature_id FK from nullOnDelete to cascadeOnDelete.
*
* nullOnDelete orphans child features when a parent is removed they lose
* their hierarchy but still exist with a null parent_feature_id. Children
* that belong to a pool have no meaning without their parent, so cascade
* deletion is the correct behaviour.
*
* Fixes #40
*/
public function up(): void
{
Schema::table('entitlement_features', function (Blueprint $table) {
$table->dropForeign(['parent_feature_id']);
$table->foreign('parent_feature_id')
->references('id')
->on('entitlement_features')
->cascadeOnDelete();
});
}
/**
* Revert to the original nullOnDelete behaviour.
*/
public function down(): void
{
Schema::table('entitlement_features', function (Blueprint $table) {
$table->dropForeign(['parent_feature_id']);
$table->foreign('parent_feature_id')
->references('id')
->on('entitlement_features')
->nullOnDelete();
});
}
};