validate([ 'from' => ['nullable', 'date'], 'to' => ['nullable', 'date'], 'include_bots' => ['nullable', 'boolean'], ]); $from = isset($validated['from']) ? Carbon::parse($validated['from'])->startOfDay() : Carbon::now()->subDays(60)->startOfDay(); $to = isset($validated['to']) ? Carbon::parse($validated['to'])->endOfDay() : Carbon::now()->endOfDay(); $includeBots = (bool) ($validated['include_bots'] ?? false); $documents = $this->documentCounts($from, $to, $includeBots); return Inertia::render('Admin/DocumentAccess', [ 'documents' => $documents, 'daily' => $this->dailyCounts($from, $to, $includeBots), 'filters' => [ 'from' => $from->toDateString(), 'to' => $to->toDateString(), 'include_bots' => $includeBots, ], 'totals' => [ 'downloads' => $documents->sum('downloads'), 'documents_touched' => $documents->count(), ], // Drives the caveat shown in the UI. 'ip_data_starts' => self::IP_DATA_STARTS, 'range_predates_ip_data' => $from->lt(Carbon::parse(self::IP_DATA_STARTS)), ]); } /** * Download counts per document, plus unique visitors where the underlying * rows actually carry an IP. */ private function documentCounts(Carbon $from, Carbon $to, bool $includeBots) { $query = DocumentAccess::query() ->join('documents', 'documents.id', '=', 'document_accesses.document_id') ->whereBetween('document_accesses.accessed_at', [$from, $to]); if (! $includeBots) { $query->excludingBots(); } return $query ->groupBy('documents.id', 'documents.title') ->orderByDesc('downloads') ->get([ 'documents.id', 'documents.title', DB::raw('count(*) as downloads'), DB::raw('count(distinct document_accesses.ip_address) as unique_visitors'), DB::raw('max(document_accesses.accessed_at) as last_accessed'), ]) ->map(fn ($row) => [ 'id' => $row->id, 'title' => $row->title, 'downloads' => (int) $row->downloads, 'unique_visitors' => (int) $row->unique_visitors, 'last_accessed' => Carbon::parse($row->last_accessed)->format('m/d/Y'), ]); } /** * Downloads per day, for the trend chart. */ private function dailyCounts(Carbon $from, Carbon $to, bool $includeBots) { $query = DocumentAccess::query() ->whereBetween('accessed_at', [$from, $to]); if (! $includeBots) { $query->excludingBots(); } return $query ->groupBy('day') ->orderBy('day') ->get([ DB::raw('date(accessed_at) as day'), DB::raw('count(*) as downloads'), ]) ->map(fn ($row) => [ 'day' => Carbon::parse($row->day)->format('m/d'), 'downloads' => (int) $row->downloads, ]); } }