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 AnalyticsSessionRepository extends BaseRepository | |
| { | |
| public function __construct(private readonly UuidGenerator $ids) {} | |
| protected function table(): string | |
| { | |
| return 'analytics_sessions'; | |
| } | |
| protected function hasDeletedAtColumn(): bool | |
| { | |
| return false; | |
| } | |
| public function findById(string $id): ?\stdClass | |
| { | |
| return DB::table($this->table())->where('id', $id)->first(); | |
| } | |
| /** | |
| * Open a new session. Returns the new session id. | |
| * $data must include visitor_id, entry_path, started_at, and any geo/UA/UTM fields. | |
| */ | |
| public function open(array $data): string | |
| { | |
| $id = $this->ids->generate(); | |
| $now = $data['started_at'] ?? Carbon::now()->toDateTimeString(); | |
| DB::table($this->table())->insert(array_merge($data, [ | |
| 'id' => $id, | |
| 'status' => 'open', | |
| 'page_count' => 0, | |
| 'is_bot' => $data['is_bot'] ?? 0, | |
| 'created_at' => $now, | |
| 'updated_at' => $now, | |
| ])); | |
| return $id; | |
| } | |
| /** | |
| * Called only by AnalyticsSessionTransitioner after validating the open→closed edge. | |
| */ | |
| public function close(string $id, string $exitPath, int $durationSeconds): void | |
| { | |
| $now = Carbon::now()->toDateTimeString(); | |
| DB::table($this->table())->where('id', $id)->update([ | |
| 'status' => 'closed', | |
| 'ended_at' => $now, | |
| 'exit_path' => $exitPath, | |
| 'duration_seconds' => $durationSeconds, | |
| 'updated_at' => $now, | |
| ]); | |
| } | |
| public function incrementPageCount(string $id): void | |
| { | |
| DB::table($this->table())->where('id', $id)->increment('page_count', 1, [ | |
| 'updated_at' => Carbon::now()->toDateTimeString(), | |
| ]); | |
| } | |
| public function updateExitPath(string $id, string $path): void | |
| { | |
| DB::table($this->table())->where('id', $id)->update([ | |
| 'exit_path' => $path, | |
| 'updated_at' => Carbon::now()->toDateTimeString(), | |
| ]); | |
| } | |
| /** | |
| * Open a session using the client-supplied UUID as the row id. | |
| * Idempotent: silently no-ops if that session_id already exists | |
| * (handles duplicate beacon delivery or concurrent requests). | |
| */ | |
| public function openIdempotent(string $sessionId, array $data): void | |
| { | |
| $exists = DB::table($this->table())->where('id', $sessionId)->exists(); | |
| if ($exists) { | |
| return; | |
| } | |
| $now = Carbon::now()->toDateTimeString(); | |
| DB::table($this->table())->insert(array_merge($data, [ | |
| 'id' => $sessionId, | |
| 'status' => 'open', | |
| 'page_count' => 0, | |
| 'is_bot' => 0, | |
| 'started_at' => $now, | |
| 'created_at' => $now, | |
| 'updated_at' => $now, | |
| ])); | |
| } | |
| } | |