Deploy Ladill Link / deploy (push) Successful in 32s
Record parsed user-agent and geo data on clicks, then surface breakdowns and richer recent-click context on per-link and account analytics views. Co-authored-by: Cursor <cursoragent@cursor.com>
62 lines
2.0 KiB
PHP
62 lines
2.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Link;
|
|
|
|
use App\Models\LinkClick;
|
|
use App\Models\LinkWallet;
|
|
use App\Models\ShortLink;
|
|
use App\Support\Link\LinkGeoResolver;
|
|
use App\Support\Link\LinkUserAgentParser;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class LinkClickRecorder
|
|
{
|
|
public function record(ShortLink $link, Request $request): void
|
|
{
|
|
$ipHash = hash('sha256', $request->ip().'|'.config('app.key'));
|
|
$windowHours = (int) config('link.click_unique_window_hours', 24);
|
|
$isUnique = ! LinkClick::query()
|
|
->where('short_link_id', $link->id)
|
|
->where('ip_hash', $ipHash)
|
|
->where('clicked_at', '>=', now()->subHours($windowHours))
|
|
->exists();
|
|
|
|
$parsed = LinkUserAgentParser::parse((string) $request->userAgent());
|
|
|
|
DB::transaction(function () use ($link, $request, $ipHash, $isUnique, $parsed) {
|
|
LinkClick::create([
|
|
'short_link_id' => $link->id,
|
|
'ip_hash' => $ipHash,
|
|
'user_agent' => $this->truncate($request->userAgent(), 512),
|
|
'referer' => $this->truncate($request->header('referer'), 512),
|
|
'device_type' => $parsed['device_type'],
|
|
'browser' => $parsed['browser'],
|
|
'os' => $parsed['os'],
|
|
'country' => LinkGeoResolver::countryCode($request),
|
|
'is_unique' => $isUnique,
|
|
'clicked_at' => now(),
|
|
]);
|
|
|
|
$link->increment('clicks_total');
|
|
if ($isUnique) {
|
|
$link->increment('unique_clicks_total');
|
|
}
|
|
$link->update(['last_clicked_at' => now()]);
|
|
|
|
LinkWallet::query()
|
|
->where('user_id', $link->user_id)
|
|
->increment('clicks_total');
|
|
});
|
|
}
|
|
|
|
private function truncate(?string $value, int $max): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
return mb_strlen($value) > $max ? mb_substr($value, 0, $max) : $value;
|
|
}
|
|
}
|