info('Starting optimized content recap migration...'); $legacyConn = DB::connection('mysql_second'); // 1. Migrate Classifications first (Small table, can handle simply) $this->migrateClassifications(); // 2. Pre-cache migrated IDs using their associated media records // Using 'content-recaps' collection to match the (typo) name in ContentRecapForm $this->migratedIds = DB::table('media') ->where('model_type', ContentRecap::class) ->where('collection_name', 'content-recaps') ->pluck('model_id') ->flip() ->all(); // 3. Migrate Content Recaps $query = $legacyConn->table('content_recap'); $totalCount = $query->count(); if ($totalCount === 0) { $this->warn('No content recaps found in legacy database.'); return; } $this->info("Found {$totalCount} legacy content recap records."); $query->orderBy('id')->chunk(50, function ($chunk) { foreach ($chunk as $legacy) { $this->processRecap($legacy); } }); $this->newLine(); $this->info('Content recap migration completed successfully!'); } /** * Migrate classifications related to content recaps. */ private function migrateClassifications(): void { $this->info('Migrating classifications...'); $legacyConn = DB::connection('mysql_second'); $legacyRecapClasses = $legacyConn->table('content_recap_classifications')->get(); foreach ($legacyRecapClasses as $legacyClass) { DB::table('classifications')->updateOrInsert( ['name' => $legacyClass->name], [ 'is_active' => IsActive::ACTIVE->value, 'created_at' => $legacyClass->created_at ?? now(), 'updated_at' => $legacyClass->updated_at ?? now(), 'deleted_at' => $legacyClass->deleted_at, ] ); } } /** * Process a single legacy content recap record. */ private function processRecap($legacy): void { $idText = "[#{$legacy->id}]"; $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->title, 40).'... '); try { // Find classification in master table $classificationId = DB::table('classifications') ->where('name', $legacy->classification) ->value('id'); if (! $classificationId) { $this->output->writeln('CLASS NOT FOUND'); return; } // Step 1: Update/Insert record DB::table('content_recaps')->updateOrInsert( ['id' => $legacy->id], [ 'classification_id' => $classificationId, 'title' => $legacy->title, 'link' => Str::limit($legacy->link, 255, ''), 'posting_date' => $legacy->posting_date === '0000-00-00' ? now()->toDateString() : $legacy->posting_date, 'type' => $this->mapContentType($legacy->content_type), 'channel' => $this->mapChannel($legacy->channel), 'social_media' => $this->mapSocialMedia($legacy->social_media), 'created_at' => $legacy->created_at ?? now(), 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); // Step 2: Media Migration if (isset($this->migratedIds[$legacy->id])) { $this->output->writeln('SKIP (Media Exists)'); return; } if (! empty($legacy->image) && $legacy->image !== 'no-image.png') { $recap = ContentRecap::withTrashed()->find($legacy->id); if ($recap) { $this->migrateMedia($recap, $legacy); } } else { $this->output->writeln('DONE'); } } catch (\Exception $e) { $this->output->writeln('FAILED: '.$e->getMessage().''); } } /** * Handle S3 media migration. */ private function migrateMedia(ContentRecap $recap, $legacy): void { $filename = trim(basename($legacy->image)); $sourcePath = 'old data/content-recaps/'.$filename; $attempts = 0; $maxAttempts = 3; $exists = false; while ($attempts < $maxAttempts) { try { $exists = Storage::disk('s3')->exists($sourcePath); break; } catch (\Exception $e) { $attempts++; usleep(200000); } } if (! $exists) { $this->output->writeln('IMAGE MISSING'); return; } try { $recap->addMediaFromDisk($sourcePath, 's3') ->withCustomProperties([ 'feature' => 'content-recaps', 'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(), ]) ->preservingOriginal() ->toMediaCollection('content-recaps', 's3'); // Match typo in ContentRecapForm $this->output->writeln('DONE + IMG'); } catch (\Exception $e) { $this->output->writeln('S3 ERROR: '.Str::limit($e->getMessage(), 50).''); } } private function mapContentType(?string $legacyType): ?int { return match (strtolower($legacyType ?? '')) { 'foto' => ContentType::FOTO->value, 'grafis' => ContentType::GRAPHIC->value, 'audio video' => ContentType::VIDEO->value, default => null, }; } private function mapChannel(?string $legacyChannel): int { return match (strtolower($legacyChannel ?? '')) { 'website' => Channel::WEBSITE->value, 'tiktok' => Channel::TIKTOK->value, 'youtube' => Channel::YOUTUBE->value, 'facebook' => Channel::FACEBOOK->value, 'instagram' => Channel::INSTAGRAM->value, 'twitter' => Channel::TWITTER->value, default => Channel::WEBSITE->value, }; } private function mapSocialMedia(?string $legacySocial): int { return match (strtolower($legacySocial ?? '')) { 'ppid diskominfo purwakarta' => SocialMedia::PPID_DISKOMINFO_PURWAKARTA->value, 'diskominfo purwakarta' => SocialMedia::DISKOMINFO_PURWAKARTA->value, 'purwakarta saber hoaks' => SocialMedia::PURWAKARTA_SABER_HOAKS->value, default => SocialMedia::DISKOMINFO_PURWAKARTA->value, }; } }