info('Starting optimized media monitoring migration...'); $legacyConn = DB::connection('mysql_second'); $query = $legacyConn->table('media_monitorings'); $totalCount = $query->count(); if ($totalCount === 0) { $this->warn('No media monitoring found in legacy database.'); return; } $this->info("Found {$totalCount} legacy media monitoring records."); // Pre-cache migrated IDs using their associated media records $this->migratedIds = DB::table('media') ->where('model_type', MediaMonitoring::class) ->where('collection_name', 'media-monitorings') ->pluck('model_id') ->flip() ->all(); $query->orderBy('id')->chunk(50, function ($chunk) { foreach ($chunk as $legacy) { $this->processMonitoring($legacy); } }); $this->newLine(); $this->info('Media monitoring migration completed successfully!'); } /** * Process a single legacy media monitoring record. */ private function processMonitoring($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 DB::table('media_monitorings')->updateOrInsert( ['id' => $legacy->id], [ 'code' => Str::limit($legacy->code, 10, ''), 'media_name' => Str::limit($legacy->media_name, 50, ''), 'title' => $legacy->title, 'channel' => $this->mapChannel($legacy->channel), 'writter' => Str::limit($legacy->writter, 50, ''), 'link' => $legacy->link, 'quote' => $legacy->quote, 'content' => $legacy->content, 'influencer' => Str::limit($legacy->influencer, 50, ''), 'keyword' => Str::limit($legacy->keyword, 100, ''), 'release_date' => $legacy->release_date, 'created_at' => $legacy->created_at ?? now(), 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); // Handle Theme $this->handleTheme($legacy); // Step 2: Skip heavy operations (media) if already has media if (isset($this->migratedIds[$legacy->id])) { $this->output->writeln('SKIP (Media Exists)'); return; } $monitoring = MediaMonitoring::withTrashed()->find($legacy->id); if (! $monitoring) { $this->output->writeln('NOT FOUND'); return; } // Step 3: Handle Media Migration if (! empty($legacy->image) && $legacy->image !== 'no-image.png') { $this->migrateMedia($monitoring, $legacy); } else { $this->output->writeln('DONE'); } } /** * Handle theme pivot and master data. */ private function handleTheme($legacy): void { $themeName = trim($legacy->theme); if (empty($themeName)) { return; } $themeName = Str::limit($themeName, 50, ''); // Ensure theme exists DB::table('themes')->updateOrInsert( ['name' => $themeName], [ 'is_active' => IsActive::ACTIVE->value, 'created_at' => now(), 'updated_at' => now(), ] ); $themeId = DB::table('themes')->where('name', $themeName)->value('id'); if ($themeId) { DB::table('media_monitoring_theme')->updateOrInsert( [ 'media_monitoring_id' => $legacy->id, 'theme_id' => $themeId, ] ); } } /** * Migrate media from S3 to Spatie Media Library. */ private function migrateMedia(MediaMonitoring $monitoring, $legacy): void { $filename = trim(basename($legacy->image)); $sourcePath = 'old data/mediamonitorings/'.$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): ".Str::limit($actualError, 50).''); return; } usleep(200000); } } if (! $exists) { $this->output->writeln('IMAGE MISSING'); return; } try { $date = Carbon::parse($legacy->created_at ?? now())->toDateString(); $monitoring->addMediaFromDisk($sourcePath, 's3') ->withCustomProperties([ 'feature' => 'media-monitorings', 'date' => $date, ]) ->preservingOriginal() ->toMediaCollection('media-monitorings', 's3'); $this->output->writeln('DONE + IMG'); } catch (\Exception $e) { $this->output->writeln('FAILED: '.$e->getMessage().''); } } /** * Map channel string to Enum value. */ 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, 'cetak' => Channel::CETAK->value, default => Channel::WEBSITE->value, }; } }