fix(portal): race-safe owner-seat initialization (firstOrCreate + catch)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 14:55:09 +02:00
parent 3bf0dfb548
commit 01383e7e5e
1 changed files with 12 additions and 8 deletions

View File

@ -4,6 +4,7 @@ namespace App\Livewire;
use App\Models\Customer; use App\Models\Customer;
use App\Models\Seat; use App\Models\Seat;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate; use Livewire\Attributes\Validate;
@ -23,16 +24,19 @@ class Users extends Component
public function mount(): void public function mount(): void
{ {
// Every customer starts with themselves as the owner seat. // Every customer starts with themselves as the owner seat. firstOrCreate
// keyed on (customer_id, email) is idempotent; the catch covers the
// concurrent-first-visit race against the unique index.
$customer = $this->customer(); $customer = $this->customer();
if ($customer !== null && $customer->seats()->count() === 0) { if ($customer !== null && $customer->seats()->count() === 0) {
$customer->seats()->create([ try {
'email' => $customer->email, $customer->seats()->firstOrCreate(
'name' => $customer->name, ['email' => $customer->email],
'role' => 'owner', ['name' => $customer->name, 'role' => 'owner', 'status' => 'active', 'invited_at' => now()],
'status' => 'active', );
'invited_at' => now(), } catch (UniqueConstraintViolationException) {
]); // Another concurrent first visit created it — fine.
}
} }
} }