mdn-backend / app /Domain /Analytics /AnalyticsSessionRepository.php
internationalscholarsprogram's picture
feat(analytics): website analytics pipeline — ingestion, rollup, admin query
45dc401
Raw
History Blame Contribute Delete
3.15 kB
<?php
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,
]));
}
}