Merge branch 'refactor/integrations' into dev

This commit is contained in:
2026-09-07 11:12:16 -03:00
40 changed files with 1914 additions and 379 deletions

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('integration_instances', function (Blueprint $table): void {
$table->id();
$table->string('integration_code');
$table->string('name');
$table->longText('integration_data')->nullable(); // Encrypted JSON.
$table->timestamps();
$table->foreign('integration_code')->references('integration_code')->on('integrations')->restrictOnDelete();
// Allows associations to enforce one instance per integration and owner.
$table->unique(['id', 'integration_code'], 'integration_instances_id_code_unique');
});
}
public function down(): void
{
Schema::dropIfExists('integration_instances');
}
};

View File

@@ -0,0 +1,81 @@
<?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
{
public function up(): void
{
// MySQL commits DDL separately; allow retrying after a partially applied migration.
if (! Schema::hasColumn('client_integrations', 'integration_instance_id')) {
Schema::table('client_integrations', function (Blueprint $table): void {
$table->unsignedBigInteger('integration_instance_id')->nullable();
});
}
DB::table('client_integrations')->whereNull('integration_instance_id')->orderBy('id')->chunkById(100, function ($associations): void {
foreach ($associations as $association) {
DB::transaction(function () use ($association): void {
$instanceId = DB::table('integration_instances')->insertGetId([
'integration_code' => $association->integration_code,
'name' => $association->integration_code.' / client '.$association->client_id,
// Copy ciphertext verbatim: no decryption or re-encryption during migration.
'integration_data' => $association->integration_data,
'created_at' => $association->created_at,
'updated_at' => $association->updated_at,
]);
DB::table('client_integrations')->where('id', $association->id)->update([
'integration_instance_id' => $instanceId,
]);
});
}
});
Schema::table('client_integrations', function (Blueprint $table): void {
$table->unsignedBigInteger('integration_instance_id')->nullable(false)->change();
});
$hasInstanceForeignKey = collect(Schema::getForeignKeys('client_integrations'))
->contains(fn (array $key): bool => $key['columns'] === ['integration_instance_id', 'integration_code']);
if (! $hasInstanceForeignKey) {
Schema::table('client_integrations', function (Blueprint $table): void {
$table->foreign(['integration_instance_id', 'integration_code'], 'client_integrations_instance_code_fk')
->references(['id', 'integration_code'])->on('integration_instances')->restrictOnDelete();
});
}
if (Schema::hasColumn('client_integrations', 'integration_data')) {
Schema::table('client_integrations', function (Blueprint $table): void {
$table->dropColumn('integration_data');
});
}
}
public function down(): void
{
Schema::table('client_integrations', function (Blueprint $table): void {
$table->longText('integration_data')->nullable();
});
DB::table('client_integrations')->orderBy('id')->chunkById(100, function ($associations): void {
foreach ($associations as $association) {
DB::table('client_integrations')->where('id', $association->id)->update([
'integration_data' => DB::table('integration_instances')
->where('id', $association->integration_instance_id)->value('integration_data'),
]);
}
});
Schema::table('client_integrations', function (Blueprint $table): void {
// MySQL uses the short name; SQLite needs the columns to rebuild the table.
$table->dropForeign('client_integrations_instance_code_fk')
->columns(['integration_instance_id', 'integration_code']);
$table->dropColumn('integration_instance_id');
});
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('website_type_integrations', function (Blueprint $table): void {
$table->id();
$table->string('website_type_code');
$table->string('integration_code');
$table->unsignedBigInteger('integration_instance_id');
$table->timestamps();
$table->foreign('website_type_code')->references('codigo')->on('website_type')->cascadeOnDelete();
$table->foreign(['integration_instance_id', 'integration_code'], 'website_type_integrations_instance_code_fk')
->references(['id', 'integration_code'])->on('integration_instances')->restrictOnDelete();
$table->unique(['website_type_code', 'integration_code'], 'website_type_integrations_owner_code_unique');
});
}
public function down(): void
{
Schema::dropIfExists('website_type_integrations');
}
};

View File

@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('integrations', function (Blueprint $table): void {
$table->renameColumn('requires_client_configuration', 'requires_configuration');
});
}
public function down(): void
{
Schema::table('integrations', function (Blueprint $table): void {
$table->renameColumn('requires_configuration', 'requires_client_configuration');
});
}
};

View File

@@ -17,7 +17,7 @@ class EmailIntegrationSeeder extends Seeder
[
'name' => 'Email',
'url' => null,
'requires_client_configuration' => false,
'requires_configuration' => false,
'integration_data_schema' => [
'MAIL_MAILER' => 'required|string|in:smtp',
'MAIL_SCHEME' => 'required|string|in:smtp',

View File

@@ -17,7 +17,7 @@ class TelepagosIntegrationSeeder extends Seeder
[
'name' => 'Telepagos',
'url' => 'https://api.telepagos.com.ar',
'requires_client_configuration' => true,
'requires_configuration' => true,
'integration_data_schema' => [
'username' => 'required|string',
'password' => 'required|string',
@@ -30,7 +30,7 @@ class TelepagosIntegrationSeeder extends Seeder
[
'name' => 'Telepagos Homologación',
'url' => 'https://api.homo.telepagos.com.ar',
'requires_client_configuration' => true,
'requires_configuration' => true,
'integration_data_schema' => [
'username' => 'required|string',
'password' => 'required|string',