feat: separate tenant domain and base path, update related models, requests, and tests
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<?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
|
||||
{
|
||||
$locations = DB::table('tenants')
|
||||
->select(['id', 'dominio'])
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->mapWithKeys(function (object $tenant): array {
|
||||
[$domain, $basePath] = $this->splitTenantLocation($tenant->dominio, $tenant->id);
|
||||
|
||||
return [$tenant->id => compact('domain', 'basePath')];
|
||||
});
|
||||
$duplicates = $locations
|
||||
->groupBy(fn (array $location): string => $location['domain'].'|'.$location['basePath'])
|
||||
->filter(fn ($matches): bool => $matches->count() > 1);
|
||||
|
||||
if ($duplicates->isNotEmpty()) {
|
||||
throw new RuntimeException(
|
||||
'Cannot create the tenant domain/base-path unique index; duplicates exist: '
|
||||
.$duplicates->keys()->implode(', ')
|
||||
);
|
||||
}
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->string('base_path')->default('/')->after('dominio');
|
||||
$table->dropUnique('tenants_dominio_unique');
|
||||
});
|
||||
|
||||
foreach ($locations as $tenantId => $location) {
|
||||
DB::table('tenants')
|
||||
->where('id', $tenantId)
|
||||
->update([
|
||||
'dominio' => $location['domain'],
|
||||
'base_path' => $location['basePath'],
|
||||
]);
|
||||
}
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->unique(
|
||||
['dominio', 'base_path'],
|
||||
'tenants_dominio_base_path_unique',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropUnique('tenants_dominio_base_path_unique');
|
||||
});
|
||||
|
||||
DB::table('tenants')
|
||||
->select(['id', 'dominio', 'base_path'])
|
||||
->orderBy('id')
|
||||
->each(function (object $tenant): void {
|
||||
$tenantKey = $tenant->dominio.($tenant->base_path === '/' ? '' : $tenant->base_path);
|
||||
|
||||
DB::table('tenants')
|
||||
->where('id', $tenant->id)
|
||||
->update(['dominio' => $tenantKey]);
|
||||
});
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropColumn('base_path');
|
||||
$table->unique('dominio', 'tenants_dominio_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/** @return array{string, string} */
|
||||
private function splitTenantLocation(mixed $value, int $tenantId): array
|
||||
{
|
||||
if (! is_string($value) || trim(urldecode($value)) === '') {
|
||||
throw new RuntimeException("Cannot migrate tenant location for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
$decodedValue = trim(urldecode($value));
|
||||
$candidate = str_contains($decodedValue, '://')
|
||||
? $decodedValue
|
||||
: "//{$decodedValue}";
|
||||
$host = parse_url($candidate, PHP_URL_HOST);
|
||||
$path = parse_url($candidate, PHP_URL_PATH) ?: '/';
|
||||
|
||||
if (! is_string($host) || $host === '') {
|
||||
throw new RuntimeException("Cannot migrate tenant location '{$value}' for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
$segments = array_values(array_filter(
|
||||
explode('/', preg_replace('#/+#', '/', $path) ?? ''),
|
||||
static fn (string $segment): bool => $segment !== '',
|
||||
));
|
||||
|
||||
if (array_intersect($segments, ['.', '..']) !== []) {
|
||||
throw new RuntimeException("Cannot migrate tenant base path '{$path}' for tenant {$tenantId}.");
|
||||
}
|
||||
|
||||
return [
|
||||
strtolower($host),
|
||||
$segments === [] ? '/' : '/'.implode('/', $segments),
|
||||
];
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user