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; $exists = false; $attempts = 0; $maxAttempts = 3; while ($attempts < $maxAttempts) { try { $exists = Storage::disk('s3')->exists($sourcePath); break; } catch (\Exception $e) { $attempts++; if ($attempts >= $maxAttempts) { $actualError = $e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage(); $this->output->writeln("S3 ERROR ({$attempts}x): ".$actualError.''); return; } usleep(200000); } } if (! $exists) { $this->output->writeln('IMAGE MISSING'); return; } try { $date = Carbon::parse($legacy->created_at ?? now())->toDateString(); $news->addMediaFromDisk($sourcePath, 's3') ->withCustomProperties([ 'feature' => 'news', 'date' => $date, ]) ->preservingOriginal() ->toMediaCollection('news', 's3'); $this->output->writeln('DONE + IMG'); } catch (\Exception $e) { $this->output->writeln("FAILED: {$e->getMessage()}"); } } }