fix: serialize datacenter delete (lock+deactivate+recheck); store logo before deleting old

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 14:58:07 +02:00
parent 01383e7e5e
commit 249efa0553
2 changed files with 24 additions and 10 deletions

View File

@ -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);
}

View File

@ -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;
}