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); } }