argument('file'); if (! is_readable($file)) { $this->error("Cannot read file: {$file}"); return self::FAILURE; } $source = $this->option('source'); $dryRun = $this->option('dry-run'); // Only import accesses for documents that still exist; the FK would // reject the rest and a deleted document has no dashboard row anyway. $knownIds = Document::pluck('id')->flip(); $handle = fopen($file, 'r'); $rows = []; $parsed = $skippedUnknown = $unmatched = 0; while (($line = fgets($handle)) !== false) { if (! preg_match(self::LINE, $line, $m)) { // Most lines are ordinary page views, not downloads. if (str_contains($line, '/download')) { $unmatched++; } continue; } [, $timestamp, $documentId, $userAgent] = $m; if (! $knownIds->has((int) $documentId)) { $skippedUnknown++; continue; } $rows[] = [ 'document_id' => (int) $documentId, 'accessed_at' => Carbon::createFromFormat('d/M/Y:H:i:s O', $timestamp)->utc(), 'ip_address' => null, // never recorded by the old log format 'user_agent' => $userAgent, 'source' => $source, 'created_at' => now(), 'updated_at' => now(), ]; $parsed++; } fclose($handle); $this->info("Parsed {$parsed} successful downloads from {$file}"); if ($skippedUnknown > 0) { $this->warn("Skipped {$skippedUnknown} referring to documents that no longer exist"); } if ($unmatched > 0) { $this->warn("Skipped {$unmatched} download lines that were not status 200 (aborted or failed)"); } if ($dryRun) { $this->comment('Dry run: nothing written.'); return self::SUCCESS; } // Idempotency: a re-run must not double-count. Every row from a given // log carries the same source, so clearing that source first makes the // import safely repeatable. $existing = DocumentAccess::where('source', $source)->count(); if ($existing > 0) { if (! $this->confirm("{$existing} rows with source '{$source}' already exist. Replace them?", true)) { $this->comment('Aborted; nothing written.'); return self::SUCCESS; } DocumentAccess::where('source', $source)->delete(); } foreach (array_chunk($rows, 500) as $chunk) { DocumentAccess::insert($chunk); } $this->info('Imported '.count($rows)." rows with source '{$source}'."); return self::SUCCESS; } }