option('date') ? Carbon::parse($this->option('date')) : Carbon::yesterday(); if ($date->isToday()) { $this->error('Cannot aggregate today\'s data — it is still being collected.'); return self::FAILURE; } $websites = $this->option('website') ? Website::where('id', $this->option('website'))->get() : Website::all(); if ($websites->isEmpty()) { $this->warn('No websites found.'); return self::SUCCESS; } $this->info("Aggregating data for {$date->toDateString()} across {$websites->count()} website(s)..."); $bar = $this->output->createProgressBar($websites->count()); $bar->start(); foreach ($websites as $website) { $this->aggregateForWebsite($website, $date); $bar->advance(); } $bar->finish(); $this->newLine(); $this->info('Aggregation complete.'); return self::SUCCESS; } private function aggregateForWebsite(Website $website, Carbon $date): void { $dayStart = $date->copy()->startOfDay(); $dayEnd = $date->copy()->endOfDay(); DB::transaction(function () use ($website, $date, $dayStart, $dayEnd) { $this->aggregateDaily($website, $date, $dayStart, $dayEnd); $this->aggregatePageDaily($website, $date, $dayStart, $dayEnd); }); } private function aggregateDaily(Website $website, Carbon $date, Carbon $dayStart, Carbon $dayEnd): void { $visitors = PageView::where('website_id', $website->id) ->whereBetween('viewed_at', [$dayStart, $dayEnd]) ->distinct('visitor_id') ->count('visitor_id'); $sessions = AnalyticsSession::where('website_id', $website->id) ->whereBetween('started_at', [$dayStart, $dayEnd]) ->count(); $page_views = PageView::where('website_id', $website->id) ->whereBetween('viewed_at', [$dayStart, $dayEnd]) ->count(); $bounce_count = AnalyticsSession::where('website_id', $website->id) ->whereBetween('started_at', [$dayStart, $dayEnd]) ->where('is_bounce', true) ->count(); $total_duration = (int) AnalyticsSession::where('website_id', $website->id) ->whereBetween('started_at', [$dayStart, $dayEnd]) ->sum('duration'); DailyAggregate::updateOrCreate( ['website_id' => $website->id, 'date' => $date->toDateString()], compact('visitors', 'sessions', 'page_views', 'bounce_count', 'total_duration'), ); } private function aggregatePageDaily(Website $website, Carbon $date, Carbon $dayStart, Carbon $dayEnd): void { $pages = PageView::where('website_id', $website->id) ->whereBetween('viewed_at', [$dayStart, $dayEnd]) ->select( 'path', DB::raw('COUNT(*) as views'), DB::raw('COUNT(DISTINCT visitor_id) as unique_views'), DB::raw('COALESCE(SUM(time_on_page), 0) as total_time_on_page'), ) ->groupBy('path') ->get(); $bouncePaths = AnalyticsSession::where('website_id', $website->id) ->whereBetween('started_at', [$dayStart, $dayEnd]) ->where('is_bounce', true) ->whereNotNull('entry_page') ->select('entry_page as path', DB::raw('COUNT(*) as bounces')) ->groupBy('entry_page') ->pluck('bounces', 'path'); PageDailyAggregate::where('website_id', $website->id) ->where('date', $date->toDateString()) ->delete(); $rows = $pages->map(fn ($page) => [ 'website_id' => $website->id, 'date' => $date->toDateString(), 'path' => $page->path, 'views' => $page->views, 'unique_views' => $page->unique_views, 'total_time_on_page' => $page->total_time_on_page, 'bounces' => $bouncePaths->get($page->path, 0), 'created_at' => now(), 'updated_at' => now(), ])->toArray(); foreach (array_chunk($rows, 500) as $chunk) { PageDailyAggregate::insert($chunk); } } }