From 249efa0553778a98b715e5adf9c8bb400ea4583f Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 14:58:07 +0200 Subject: [PATCH] fix: serialize datacenter delete (lock+deactivate+recheck); store logo before deleting old Co-Authored-By: Claude Opus 4.8 --- .../Admin/ConfirmDeleteDatacenter.php | 24 ++++++++++++++----- app/Livewire/Settings.php | 10 ++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Admin/ConfirmDeleteDatacenter.php b/app/Livewire/Admin/ConfirmDeleteDatacenter.php index 08dd267..e88e8cf 100644 --- a/app/Livewire/Admin/ConfirmDeleteDatacenter.php +++ b/app/Livewire/Admin/ConfirmDeleteDatacenter.php @@ -3,6 +3,8 @@ namespace App\Livewire\Admin; use App\Models\Datacenter; +use App\Models\Host; +use Illuminate\Support\Facades\DB; use LivewireUI\Modal\ModalComponent; /** @@ -28,14 +30,24 @@ class ConfirmDeleteDatacenter extends ModalComponent public function delete() { - $dc = Datacenter::query()->where('uuid', $this->uuid)->withCount('hosts')->first(); + // Serialize the guard with the delete: lock the datacenter row and + // re-check host membership inside the same transaction so a host created + // concurrently can't be orphaned (hosts.datacenter is a bare code). + DB::transaction(function () { + $dc = Datacenter::query()->where('uuid', $this->uuid)->lockForUpdate()->first(); + if ($dc === null) { + return; + } + // Deactivate first so HostCreate's `exists:…,active,1` rule blocks any + // new host for this code while we finish deleting. + $dc->update(['active' => false]); - // Re-check under the current state — never delete a datacenter with hosts. - if ($dc === null || $dc->hosts_count > 0) { - return $this->redirectRoute('admin.datacenters', navigate: true); - } + if (Host::query()->where('datacenter', $dc->code)->exists()) { + return; // still has hosts — leave it (deactivated) + } - $dc->delete(); + $dc->delete(); + }); return $this->redirectRoute('admin.datacenters', navigate: true); } diff --git a/app/Livewire/Settings.php b/app/Livewire/Settings.php index 824dcad..d594526 100644 --- a/app/Livewire/Settings.php +++ b/app/Livewire/Settings.php @@ -103,11 +103,13 @@ class Settings extends Component ]); if ($this->logo !== null) { - // Replace any previous logo; validated MIME/size above. - if ($this->brandLogoPath !== null) { - Storage::disk('public')->delete($this->brandLogoPath); - } + // Store the new file FIRST; only drop the old one once the write + // succeeded, so a failed upload never leaves a dangling reference. + $old = $this->brandLogoPath; $this->brandLogoPath = $this->logo->store('branding', 'public'); + if ($old !== null && $old !== $this->brandLogoPath) { + Storage::disk('public')->delete($old); + } $this->logo = null; }