GET /slots/availability, POST /slots/{id}/hold, POST /holds/{id}/confirm
and DELETE /holds/{id} on Laravel 12 and MySQL 8.
- availability cached for 10s behind a single-flight lock with a stale
copy, invalidated after commit on confirm and cancel
- oversell prevented by a conditional decrement, verified by the number
of affected rows rather than a preceding select
- idempotency enforced by a unique index on holds.idempotency_key
- active holds counted with an expires_at filter, so no background
cleanup is required for correctness
- 27 feature tests against MySQL, service documentation, Postman collection
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32 lines
894 B
PHP
32 lines
894 B
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Database\Seeders;
|
||
|
||
use App\Models\Slot;
|
||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||
use Illuminate\Database\Seeder;
|
||
|
||
class DatabaseSeeder extends Seeder
|
||
{
|
||
use WithoutModelEvents;
|
||
|
||
/**
|
||
* Три слота для ручной проверки. На слоте с одним местом конфликт оверсела
|
||
* воспроизводится двумя запросами.
|
||
*/
|
||
public function run(): void
|
||
{
|
||
$start = now()->startOfHour()->addHour();
|
||
|
||
foreach ([10, 5, 1] as $index => $capacity) {
|
||
Slot::query()->create([
|
||
'starts_at' => $start->copy()->addHours($index),
|
||
'ends_at' => $start->copy()->addHours($index + 1),
|
||
'capacity' => $capacity,
|
||
'remaining' => $capacity,
|
||
]);
|
||
}
|
||
}
|
||
}
|