Spaces:
Running
Running
| declare(strict_types=1); | |
| namespace App\Domain\Analytics; | |
| use App\Infrastructure\Database\BaseRepository; | |
| use App\Infrastructure\Ids\UuidGenerator; | |
| use Illuminate\Support\Carbon; | |
| use Illuminate\Support\Facades\DB; | |
| final class AnalyticsEventRepository extends BaseRepository | |
| { | |
| public function __construct(private readonly UuidGenerator $ids) {} | |
| protected function table(): string | |
| { | |
| return 'analytics_events'; | |
| } | |
| protected function hasDeletedAtColumn(): bool | |
| { | |
| return false; | |
| } | |
| /** | |
| * Append a single event. The only write method on this table. | |
| * occurred_at is server-assigned here to prevent client clock skew. | |
| * No UPDATE or DELETE is ever called on this table β a DB trigger enforces this. | |
| * | |
| * Returns the new event id. | |
| */ | |
| public function record(array $data): string | |
| { | |
| $id = $this->ids->generate(); | |
| DB::table($this->table())->insert(array_merge($data, [ | |
| 'id' => $id, | |
| 'occurred_at' => Carbon::now()->format('Y-m-d H:i:s.u'), | |
| 'bot_score' => $data['bot_score'] ?? 0, | |
| ])); | |
| return $id; | |
| } | |
| /** | |
| * Append multiple events in one INSERT. All share the same occurred_at wall-clock | |
| * instant; for strict ordering within the batch the caller may pre-populate | |
| * occurred_at with microsecond offsets. | |
| */ | |
| public function recordBatch(array $events): void | |
| { | |
| if (empty($events)) { | |
| return; | |
| } | |
| $now = Carbon::now()->format('Y-m-d H:i:s.u'); | |
| $rows = array_map(function (array $event) use ($now): array { | |
| return array_merge($event, [ | |
| 'id' => $this->ids->generate(), | |
| 'occurred_at' => $event['occurred_at'] ?? $now, | |
| 'bot_score' => $event['bot_score'] ?? 0, | |
| ]); | |
| }, $events); | |
| foreach (array_chunk($rows, 500) as $chunk) { | |
| DB::table($this->table())->insert($chunk); | |
| } | |
| } | |
| } | |