phase 8 and 9

This commit is contained in:
Stefanescu Mihai
2026-03-19 18:42:19 +02:00
parent d75cabfeec
commit 82ebb697f3
94 changed files with 4498 additions and 749 deletions
@@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\V1\EventResource;
use App\Http\Resources\V1\PageResource;
use App\Http\Resources\V1\ReferrerResource;
use App\Http\Resources\V1\StatsResource;
use App\Http\Resources\V1\VisitorBreakdownResource;
use App\Http\Resources\V1\WebsiteResource;
use App\Models\Website;
use App\Services\AnalyticsQueryService;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Carbon;
class WebsiteController extends Controller
{
public function index(Request $request): AnonymousResourceCollection
{
$websites = $request->user()->websites()->orderBy('name')->get();
return WebsiteResource::collection($websites);
}
public function stats(Request $request, Website $website): StatsResource
{
$this->authorizeWebsite($request, $website);
[$from, $to] = $this->parseDateRange($request);
$service = AnalyticsQueryService::for($website, $from, $to);
return new StatsResource([
'from' => $from->toDateString(),
'to' => $to->toDateString(),
'metrics' => $service->summary(),
'timeseries' => $service->visitorsOverTime(),
]);
}
public function pages(Request $request, Website $website): AnonymousResourceCollection
{
$this->authorizeWebsite($request, $website);
[$from, $to] = $this->parseDateRange($request);
$limit = $this->parseLimit($request);
$service = AnalyticsQueryService::for($website, $from, $to);
return PageResource::collection($service->topPages($limit));
}
public function referrers(Request $request, Website $website): AnonymousResourceCollection
{
$this->authorizeWebsite($request, $website);
[$from, $to] = $this->parseDateRange($request);
$limit = $this->parseLimit($request);
$service = AnalyticsQueryService::for($website, $from, $to);
return ReferrerResource::collection($service->trafficSources($limit));
}
public function events(Request $request, Website $website): AnonymousResourceCollection
{
$this->authorizeWebsite($request, $website);
[$from, $to] = $this->parseDateRange($request);
$limit = $this->parseLimit($request);
$service = AnalyticsQueryService::for($website, $from, $to);
return EventResource::collection($service->eventsOverview($limit));
}
public function visitors(Request $request, Website $website): VisitorBreakdownResource
{
$this->authorizeWebsite($request, $website);
[$from, $to] = $this->parseDateRange($request);
$service = AnalyticsQueryService::for($website, $from, $to);
return new VisitorBreakdownResource([
'from' => $from->toDateString(),
'to' => $to->toDateString(),
'countries' => $service->visitorsByCountry(20)->map(fn ($r) => [
'country' => $r->country,
'visitors' => $r->visitors,
]),
'browsers' => $service->visitorsByBrowser(10)->map(fn ($r) => [
'browser' => $r->browser,
'visitors' => $r->visitors,
]),
'operating_systems' => $service->visitorsByOS(10)->map(fn ($r) => [
'os' => $r->os,
'visitors' => $r->visitors,
]),
'device_types' => $service->visitorsByDeviceType(5)->map(fn ($r) => [
'device_type' => $r->device_type,
'visitors' => $r->visitors,
]),
'languages' => $service->visitorsByLanguage(10)->map(fn ($r) => [
'language' => $r->language,
'visitors' => $r->visitors,
]),
'new_vs_returning' => $service->newVsReturning(),
]);
}
private function authorizeWebsite(Request $request, Website $website): void
{
if ($website->user_id !== $request->user()->id) {
abort(403, 'You do not own this website.');
}
}
private function parseDateRange(Request $request): array
{
$request->validate([
'from' => 'nullable|date|before_or_equal:to',
'to' => 'nullable|date|after_or_equal:from',
]);
$from = $request->has('from')
? Carbon::parse($request->get('from'))
: Carbon::today()->subDays(6);
$to = $request->has('to')
? Carbon::parse($request->get('to'))
: Carbon::today();
return [$from, $to];
}
private function parseLimit(Request $request): int
{
return min((int) $request->get('limit', 50), 200);
}
}