7 changed files with 182 additions and 69 deletions
@ -1,34 +0,0 @@ |
|||||||
import { Controller } from '@hotwired/stimulus'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Simple analytics controller to record page visits |
|
||||||
*/ |
|
||||||
export default class extends Controller { |
|
||||||
static values = { |
|
||||||
path: String |
|
||||||
} |
|
||||||
|
|
||||||
connect() { |
|
||||||
// Record the visit when the controller connects
|
|
||||||
this.recordVisit(); |
|
||||||
} |
|
||||||
|
|
||||||
recordVisit() { |
|
||||||
// Get the current route path
|
|
||||||
const path = this.pathValue || window.location.pathname; |
|
||||||
|
|
||||||
// Send visit data to API
|
|
||||||
fetch('/api/visit', { |
|
||||||
method: 'POST', |
|
||||||
headers: { |
|
||||||
'Content-Type': 'application/json', |
|
||||||
}, |
|
||||||
body: JSON.stringify({ |
|
||||||
route: path |
|
||||||
}) |
|
||||||
}) |
|
||||||
.catch(error => { |
|
||||||
console.error('Error recording visit:', error); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,33 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
namespace App\Controller\Api; |
|
||||||
|
|
||||||
use App\Entity\Visit; |
|
||||||
use App\Repository\VisitRepository; |
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse; |
|
||||||
use Symfony\Component\HttpFoundation\Request; |
|
||||||
use Symfony\Component\HttpFoundation\Response; |
|
||||||
use Symfony\Component\Routing\Attribute\Route; |
|
||||||
|
|
||||||
class VisitController extends AbstractController |
|
||||||
{ |
|
||||||
#[Route('/api/visit', name: 'api_record_visit', methods: ['POST'])] |
|
||||||
public function recordVisit(Request $request, VisitRepository $visitRepository): JsonResponse |
|
||||||
{ |
|
||||||
$data = json_decode($request->getContent(), true); |
|
||||||
|
|
||||||
if (!isset($data['route']) || empty($data['route'])) { |
|
||||||
return new JsonResponse(['error' => 'Route is required'], Response::HTTP_BAD_REQUEST); |
|
||||||
} |
|
||||||
|
|
||||||
$route = $data['route']; |
|
||||||
$visit = new Visit($route); |
|
||||||
|
|
||||||
$visitRepository->save($visit); |
|
||||||
|
|
||||||
return new JsonResponse(['success' => true]); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,70 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace App\EventListener; |
||||||
|
|
||||||
|
use App\Entity\Visit; |
||||||
|
use App\Repository\VisitRepository; |
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener; |
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent; |
||||||
|
use Symfony\Component\HttpKernel\KernelEvents; |
||||||
|
use Symfony\Bundle\SecurityBundle\Security; |
||||||
|
|
||||||
|
#[AsEventListener(event: KernelEvents::REQUEST, method: 'onKernelRequest', priority: 0)] |
||||||
|
class VisitTrackingListener |
||||||
|
{ |
||||||
|
private const EXCLUDED_ROUTES = [ |
||||||
|
'/api/', |
||||||
|
'/_profiler', |
||||||
|
'/_wdt', |
||||||
|
'/service-worker.js', |
||||||
|
'/robots.txt', |
||||||
|
'/assets/', |
||||||
|
'/icons/', |
||||||
|
]; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
private readonly VisitRepository $visitRepository, |
||||||
|
private readonly Security $security, |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
public function onKernelRequest(RequestEvent $event): void |
||||||
|
{ |
||||||
|
// Only track main requests, not sub-requests |
||||||
|
if (!$event->isMainRequest()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$request = $event->getRequest(); |
||||||
|
$route = $request->getPathInfo(); |
||||||
|
|
||||||
|
// Skip tracking for excluded routes (API, profiler, assets, etc.) |
||||||
|
foreach (self::EXCLUDED_ROUTES as $excludedRoute) { |
||||||
|
if (str_starts_with($route, $excludedRoute)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Get session ID if user is logged in |
||||||
|
$sessionId = null; |
||||||
|
if ($this->security->getUser()) { |
||||||
|
// Start session if not already started |
||||||
|
if (!$request->hasSession() || !$request->getSession()->isStarted()) { |
||||||
|
$request->getSession()->start(); |
||||||
|
} |
||||||
|
$sessionId = $request->getSession()->getId(); |
||||||
|
} |
||||||
|
|
||||||
|
// Create and save the visit record |
||||||
|
$visit = new Visit($route, $sessionId); |
||||||
|
|
||||||
|
try { |
||||||
|
$this->visitRepository->save($visit); |
||||||
|
} catch (\Exception $e) { |
||||||
|
// Silently fail to avoid breaking the request |
||||||
|
// You could log this error if needed |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue