linkId); if (! $link) { return; } $ipHash = $this->hashIp($this->ip); $device = $this->detectDevice($this->userAgent ?? ''); $click = Click::create([ 'link_id' => $this->linkId, 'workspace_id' => $link->workspace_id, 'clicked_at' => now(), 'ip_hash' => $ipHash, 'device' => $device, 'os' => $this->detectOs($this->userAgent ?? ''), 'browser' => $this->detectBrowser($this->userAgent ?? ''), 'referrer_host' => $this->extractHost($this->headers['referer'][0] ?? null), 'utm_source' => $this->queryParams['utm_source'] ?? null, 'utm_medium' => $this->queryParams['utm_medium'] ?? null, 'utm_campaign' => $this->queryParams['utm_campaign'] ?? null, 'language' => substr($this->headers['accept-language'][0] ?? 'de', 0, 10), ]); // Increment Redis counters $date = now()->format('Y-m-d'); Redis::incr("clicks:{$this->linkId}:{$date}"); Redis::incr("clicks:{$link->workspace_id}:monthly:" . now()->format('Ym')); // Broadcast event for live dashboard event(new \App\Domains\Analytics\Events\ClickRecorded($click)); } private function hashIp(string $ip): string { $salt = Cache::remember('ip_salt:' . today()->format('Y-m-d'), 86400, fn () => bin2hex(random_bytes(16))); return hash('sha256', $ip . $salt); } private function detectDevice(string $ua): string { $ua = strtolower($ua); if (str_contains($ua, 'bot') || str_contains($ua, 'crawler') || str_contains($ua, 'spider')) { return 'bot'; } if (preg_match('/mobile|iphone|android.*mobile|windows phone/i', $ua)) { return 'mobile'; } if (preg_match('/ipad|android(?!.*mobile)|tablet/i', $ua)) { return 'tablet'; } if (strlen($ua) === 0) { return 'unknown'; } return 'desktop'; } private function detectOs(string $ua): ?string { if (stripos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false) return 'ios'; if (stripos($ua, 'Android') !== false) return 'android'; if (stripos($ua, 'Windows') !== false) return 'windows'; if (stripos($ua, 'Mac OS') !== false) return 'macos'; if (stripos($ua, 'Linux') !== false) return 'linux'; return null; } private function detectBrowser(string $ua): ?string { if (stripos($ua, 'Edge') !== false || stripos($ua, 'Edg/') !== false) return 'edge'; if (stripos($ua, 'Chrome') !== false) return 'chrome'; if (stripos($ua, 'Firefox') !== false) return 'firefox'; if (stripos($ua, 'Safari') !== false && stripos($ua, 'Chrome') === false) return 'safari'; return null; } private function extractHost(?string $url): ?string { if (! $url) return null; $parsed = parse_url($url); return $parsed['host'] ?? null; } }