table('news')->where('category', '0'); $totalCount = $query->count(); if ($totalCount === 0) { $this->warn('No news found with category 0.'); return; } $this->info("Found {$totalCount} legacy news records."); // Cache existing news IDs with media to avoid N+1 media queries $this->migratedIds = DB::table('media') ->where('model_type', News::class) ->where('collection_name', 'news') ->pluck('model_id') ->flip() ->all(); // Process in chunks for memory efficiency $query->orderBy('id')->chunk(50, function ($chunk) { foreach ($chunk as $legacy) { $this->processNews($legacy); } }); $this->newLine(); $this->info('News migration completed successfully!'); } /** * Process a single legacy news record. */ private function processNews($legacy): void { $idText = "[#{$legacy->id}]"; $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->title, 40).'... '); // Step 1: Basic data update (Always sync basic info) DB::table('news')->updateOrInsert( ['id' => $legacy->id], [ 'author_id' => $legacy->enhancer, 'title' => $legacy->title, 'slug' => Str::slug($legacy->title).'-'.$legacy->id, 'content' => $legacy->content, 'excerpt' => Str::limit(strip_tags($legacy->content), 160), 'link' => $legacy->link ? Str::limit($legacy->link, 255, '') : null, 'views' => 0, 'status' => NewsStatus::PUBLISHED->value, 'published_at' => $legacy->created_at, 'created_at' => $legacy->created_at ?? now(), 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); // Step 2: Skip heavy operations (tags & media) if record already has media if (isset($this->migratedIds[$legacy->id])) { $this->output->writeln('SKIP'); return; } $news = News::withTrashed()->find($legacy->id); if (! $news) { $this->output->writeln('NOT FOUND'); return; } // Step 3: Handle Tags if (! empty($legacy->tag)) { $tags = array_filter(array_map('trim', explode(',', $legacy->tag))); $news->syncTags($tags); } // Step 4: Handle Media if (! empty($legacy->image)) { $this->migrateMedia($news, $legacy); } else { $this->output->writeln('DONE'); } } /** * Migrate media for a news record. */ private function migrateMedia(News $news, $legacy): void { $filename = trim(basename($legacy->image)); $sourcePath = 'old-data/news/'.$filename; $legacyDir = dirname($sourcePath); DB::table('media')->insert([ 'model_type' => News::class, 'model_id' => $news->id, 'uuid' => (string) Str::uuid(), 'collection_name' => 'news', 'name' => pathinfo($filename, PATHINFO_FILENAME), 'file_name' => $filename, 'mime_type' => $this->guessMime($filename), 'disk' => 's3', 'conversions_disk' => 's3', 'size' => 0, 'manipulations' => '[]', 'custom_properties' => json_encode([ 'feature' => 'news', 'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(), 'legacy_dir' => $legacyDir, ]), 'generated_conversions' => '[]', 'responsive_images' => '[]', 'order_column' => 1, 'created_at' => now(), 'updated_at' => now(), ]); $this->output->writeln('DONE + IMG'); } private function guessMime(string $fileName): string { return match (strtolower(pathinfo($fileName, PATHINFO_EXTENSION))) { 'pdf' => 'application/pdf', 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', default => 'application/octet-stream', }; } }