diff --git a/app/Console/Commands/MigrateAnnouncementCommand.php b/app/Console/Commands/MigrateAnnouncementCommand.php index 868642d..5361a48 100644 --- a/app/Console/Commands/MigrateAnnouncementCommand.php +++ b/app/Console/Commands/MigrateAnnouncementCommand.php @@ -5,6 +5,7 @@ use App\Enums\AnnouncementType; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class MigrateAnnouncementCommand extends Command { @@ -20,29 +21,48 @@ class MigrateAnnouncementCommand extends Command * * @var string */ - protected $description = 'Migrate announcement data from legacy news table where category = 1'; + protected $description = 'Migrate announcement data from legacy news table where category = 1 with optimized processing'; /** * Execute the console command. */ public function handle() { - $this->info('Starting announcement migration...'); + $this->info('Starting optimized announcement migration...'); $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('news')->where('category', '1'); - // Get legacy news with category 1 (Announcements) - $legacyAnnouncements = $legacyConn->table('news') - ->where('category', '1') - ->get(); - - if ($legacyAnnouncements->isEmpty()) { - $this->warn('No announcements found with category 1.'); + $totalCount = $query->count(); + if ($totalCount === 0) { + $this->warn('No announcements found in legacy database.'); return; } - $this->withProgressBar($legacyAnnouncements, function ($legacy) { + $this->info("Found {$totalCount} legacy announcement records."); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processAnnouncement($legacy); + } + }); + + $this->newLine(); + $this->info('Announcement migration completed successfully!'); + } + + /** + * Process a single legacy announcement record. + */ + private function processAnnouncement($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->title, 40).'... '); + + try { DB::table('announcements')->updateOrInsert( ['id' => $legacy->id], [ @@ -54,9 +74,10 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Announcement migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } } diff --git a/app/Console/Commands/MigrateClassificationCommand.php b/app/Console/Commands/MigrateClassificationCommand.php index a490e72..58a587e 100644 --- a/app/Console/Commands/MigrateClassificationCommand.php +++ b/app/Console/Commands/MigrateClassificationCommand.php @@ -5,6 +5,7 @@ use App\Enums\IsActive; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class MigrateClassificationCommand extends Command { @@ -20,55 +21,113 @@ class MigrateClassificationCommand extends Command * * @var string */ - protected $description = 'Migrate classification data from legacy database'; + protected $description = 'Migrate classification and sub-classification data from legacy database with optimized processing'; /** * Execute the console command. */ public function handle() { - $this->info('Starting classification migration...'); + $this->info('Starting optimized classification migration...'); + $this->migrateClassifications(); + $this->newLine(); + $this->migrateSubClassifications(); + + $this->newLine(); + $this->info('Classification migration completed successfully!'); + } + + /** + * Migrate main classifications. + */ + private function migrateClassifications(): void + { $legacyConn = DB::connection('mysql_second'); - $newConn = DB::connection('mysql'); + $query = $legacyConn->table('classifications'); - // Migrate Classifications - $this->info('Migrating classifications...'); - $legacyClassifications = $legacyConn->table('classifications')->get(); + $totalCount = $query->count(); + $this->info("Found {$totalCount} legacy classification records."); - $this->withProgressBar($legacyClassifications, function ($legacy) { + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processClassification($legacy); + } + }); + } + + /** + * Process a single legacy classification record. + */ + private function processClassification($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} (CLA) Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('classifications')->updateOrInsert( ['id' => $legacy->id], [ 'name' => $legacy->name, 'is_active' => $legacy->status == '1' ? IsActive::ACTIVE->value : IsActive::INACTIVE->value, - 'created_at' => $legacy->created_at, - 'updated_at' => $legacy->updated_at, + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); + + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } + } + + /** + * Migrate sub-classifications. + */ + private function migrateSubClassifications(): void + { + $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('sub_classifications'); + + $totalCount = $query->count(); + $this->info("Found {$totalCount} legacy sub-classification records."); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processSubClassification($legacy); + } }); - $this->newLine(); + } - // Migrate Sub Classifications - $this->info('Migrating sub classifications...'); - $legacySubClassifications = $legacyConn->table('sub_classifications')->get(); + /** + * Process a single legacy sub-classification record. + */ + private function processSubClassification($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; - $this->withProgressBar($legacySubClassifications, function ($legacy) { + $this->output->write("{$idOutput} (SUB) Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('sub_classifications')->updateOrInsert( ['id' => $legacy->id], [ - 'classification_id' => $legacy->classification, + 'classification_id' => $legacy->classification, // Foreign key to classifications 'name' => $legacy->name, 'is_active' => $legacy->status == '1' ? IsActive::ACTIVE->value : IsActive::INACTIVE->value, - 'created_at' => $legacy->created_at, - 'updated_at' => $legacy->updated_at, + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } } diff --git a/app/Console/Commands/MigrateCompanyCommand.php b/app/Console/Commands/MigrateCompanyCommand.php index 13789d5..f19493f 100644 --- a/app/Console/Commands/MigrateCompanyCommand.php +++ b/app/Console/Commands/MigrateCompanyCommand.php @@ -7,10 +7,12 @@ use App\Enums\MediaType; use App\Enums\RoleEnum; use App\Enums\VerificationStatus; +use App\Models\Company; use App\Models\User; use Carbon\Carbon; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class MigrateCompanyCommand extends Command @@ -27,39 +29,78 @@ class MigrateCompanyCommand extends Command * * @var string */ - protected $description = 'Migrate companies, partner media, and verification requests from legacy database'; + protected $description = 'Migrate companies, partner media, and all verification documents from legacy database'; + + /** + * Cache for migrated IDs with media to avoid redundant processing. + */ + private array $migratedIds = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting company migration...'); + $this->info('Starting optimized company migration with document support...'); $legacyConn = DB::connection('mysql_second'); - $legacyCompanies = $legacyConn->table('companies') - ->whereNull('deleted_at') - ->get(); + $query = $legacyConn->table('companies'); - if ($legacyCompanies->isEmpty()) { + $totalCount = $query->count(); + if ($totalCount === 0) { $this->warn('No companies found in legacy database.'); return; } - $this->withProgressBar($legacyCompanies, function ($legacy) use ($legacyConn) { - // Find user in NEW database. Legcy enhancer is the user_id. - $newUser = DB::table('users')->where('id', $legacy->enhancer)->first(); - $legacyUser = $legacyConn->table('users')->where('id', $legacy->enhancer)->first(); + $this->info("Found {$totalCount} legacy company records."); - // If not found by ID, try by email (sometimes IDs change during user migration if not careful) + // Pre-cache migrated IDs using their associated media records + $this->migratedIds = DB::table('media') + ->where('model_type', Company::class) + ->where('collection_name', 'companies') + ->pluck('model_id') + ->flip() + ->all(); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processCompany($legacy); + } + }); + + $this->newLine(); + $this->info('Company migration completed successfully!'); + } + + /** + * Process a single legacy company record. + */ + private function processCompany($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->name, 40).'... '); + + try { + $legacyConn = DB::connection('mysql_second'); + + // Find user + $newUser = DB::table('users')->where('id', $legacy->enhancer)->first(); if (! $newUser) { $newUser = DB::table('users')->where('email', $legacy->email)->first(); + } + + if (! $newUser) { + $this->output->writeln('USER NOT FOUND'); return; } - // 1. Migrate Company + $legacyUser = $legacyConn->table('users')->where('id', $legacy->enhancer)->first(); + + // 1. Update/Insert Company DB::table('companies')->updateOrInsert( ['id' => $legacy->id], [ @@ -70,73 +111,176 @@ public function handle() 'address' => $legacy->address, 'director_name' => $legacy->director, 'director_nik' => $legacy->nick_director, - 'deed_incorporation' => str()->limit($legacy->deed_of_establishment, 150, ''), - 'trade_license' => str()->limit($legacy->trade_license, 150, ''), - 'tax_id_number' => str()->limit($legacy->tax_id_number, 150, ''), - 'taxable_enterprise' => str()->limit($legacy->taxable_enterprise, 150, ''), - 'annual_tax_return' => str()->limit($legacy->annual_tax_statement, 150, ''), - 'domicile_certificate' => str()->limit($legacy->domicile, 150, ''), - 'profile' => str()->limit($legacy->profile ?? '-', 150, ''), + 'deed_incorporation' => Str::limit($legacy->deed_of_establishment, 150, ''), + 'trade_license' => Str::limit($legacy->trade_license, 150, ''), + 'tax_id_number' => Str::limit($legacy->tax_id_number, 150, ''), + 'taxable_enterprise' => Str::limit($legacy->taxable_enterprise, 150, ''), + 'annual_tax_return' => Str::limit($legacy->annual_tax_statement, 150, ''), + 'domicile_certificate' => Str::limit($legacy->domicile, 150, ''), + 'profile' => Str::limit($legacy->profile ?? '-', 150, ''), 'created_at' => $legacy->created_at ?? now(), 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); - // 2. Migrate Verification Request - $status = $this->mapVerificationStatus($legacyUser->status); - DB::table('verification_requests')->updateOrInsert( - ['company_id' => $legacy->id], - [ - 'submitted_by' => $newUser->id, - 'status' => $status, - 'created_at' => $legacy->created_at ?? now(), - 'updated_at' => $legacy->updated_at ?? now(), - ] - ); + // 2. Verification Request & Review + $this->handleVerification($legacy, $legacyUser, $newUser); - // 3. Migrate Verification Review - $decision = $this->mapDecision($legacyUser->status); + // 3. Partner Media + $this->handlePartnerMedia($legacy); + + // 4. Media Documents Migration (Skip if already has media in 'companies' collection) + if (isset($this->migratedIds[$legacy->id])) { + $this->output->writeln('DONE (SKIP DOCS)'); + + return; + } + + $company = Company::withTrashed()->find($legacy->id); + if ($company) { + $this->migrateAllDocuments($company, $legacy); + } + + $this->output->writeln('DONE + DOCS'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } + } + + /** + * Handle verification logic. + */ + private function handleVerification($legacy, $legacyUser, $newUser): void + { + $status = $this->mapVerificationStatus($legacyUser->status); + + DB::table('verification_requests')->updateOrInsert( + ['company_id' => $legacy->id], + [ + 'submitted_by' => $newUser->id, + 'status' => $status, + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), + ] + ); + + $decision = $this->mapDecision($legacyUser->status); + if ($decision !== DecisionAdmin::NEED_REVISION->value) { $verificationRequest = DB::table('verification_requests')->where('company_id', $legacy->id)->first(); - if ($decision !== DecisionAdmin::NEED_REVISION->value) { + $adminUser = User::role(RoleEnum::ADMINISTRATOR)->first(); + + if ($verificationRequest && $adminUser) { DB::table('verification_reviews')->updateOrInsert( ['verification_request_id' => $verificationRequest->id], [ - 'reviewer_id' => User::role(RoleEnum::ADMINISTRATOR)->first()->id, + 'reviewer_id' => $adminUser->id, 'decision' => $decision, 'note' => $legacy->reject_message, - 'created_at' => Carbon::parse($legacy->validation_date)->setTime(23, 0, 0), + 'created_at' => $legacy->validation_date ? Carbon::parse($legacy->validation_date)->setTime(23, 0, 0) : now(), 'updated_at' => $legacy->updated_at ?? now(), ] ); } - - // 4. Migrate Partner Media (from legacy 'media' table matching company ID) - $legacyMediaList = $legacyConn->table('media')->where('company', $legacy->id)->get(); - foreach ($legacyMediaList as $legacyMedia) { - DB::table('partner_media')->updateOrInsert( - ['id' => $legacyMedia->id], - [ - 'company_id' => $legacy->id, - 'name' => $legacyMedia->name, - 'address' => $legacyMedia->address, - 'link' => $this->extractMediaLink($legacyMedia->link), - 'type' => $this->mapMediaType($legacyMedia->type), - 'classification' => $this->mapMediaClassification($legacyMedia->classification), - 'journalism_organization' => $legacyMedia->organization, - 'press_council_certificate' => $legacyMedia->certificate, - 'created_at' => $legacyMedia->created_at ?? now(), - 'updated_at' => $legacyMedia->updated_at ?? now(), - 'deleted_at' => $legacyMedia->deleted_at, - ] - ); - } - }); - - $this->newLine(); - $this->info('Company migration completed successfully!'); + } } + /** + * Handle partner media. + */ + private function handlePartnerMedia($legacy): void + { + $legacyConn = DB::connection('mysql_second'); + $legacyMediaList = $legacyConn->table('media')->where('company', $legacy->id)->get(); + + foreach ($legacyMediaList as $legacyMedia) { + DB::table('partner_media')->updateOrInsert( + ['id' => $legacyMedia->id], + [ + 'company_id' => $legacy->id, + 'name' => $legacyMedia->name, + 'address' => $legacyMedia->address, + 'link' => $this->extractMediaLink($legacyMedia->link), + 'type' => $this->mapMediaType($legacyMedia->type), + 'classification' => $this->mapMediaClassification($legacyMedia->classification), + 'journalism_organization' => $legacyMedia->organization, + 'press_council_certificate' => $legacyMedia->certificate, + 'created_at' => $legacyMedia->created_at ?? now(), + 'updated_at' => $legacyMedia->updated_at ?? now(), + 'deleted_at' => $legacyMedia->deleted_at, + ] + ); + } + } + + /** + * Migrate all company documents from S3. + */ + private function migrateAllDocuments(Company $company, $legacy): void + { + $docMapping = [ + 'doc_of_nick_director' => 'director_nik', + 'doc_of_deed_of_establishment' => 'deed_incorporation', + 'doc_of_trade_license' => 'trade_license', + 'doc_of_tax_id_number' => 'tax_id_number', + 'doc_of_taxable_enterprise' => 'taxable_enterprise', + 'doc_of_annual_tax_statement' => 'annual_tax_return', + 'doc_of_domicile' => 'domicile_certificate', + 'doc_of_profile' => 'profile', + ]; + + foreach ($docMapping as $legacyField => $docTypeSuffix) { + $legacyPath = trim($legacy->{$legacyField} ?? ''); + if (empty($legacyPath) || $legacyPath === '-' || $legacyPath === 'no-image.png') { + continue; + } + + // Path mapping: old data/companies/ + path from db + $sourcePath = 'old data/companies/'.$legacyPath; + + $this->importOneDocument($company, $sourcePath, $docTypeSuffix, $legacy); + } + } + + /** + * Import a single document to Media Library. + */ + private function importOneDocument(Company $company, string $sourcePath, string $docType, $legacy): void + { + $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) { + return; + } + + try { + $company->addMediaFromDisk($sourcePath, 's3') + ->withCustomProperties([ + 'feature' => 'companies', + 'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(), + 'doc_type' => $docType, // Match CompanySaveService slug pattern + ]) + ->preservingOriginal() + ->toMediaCollection('companies', 's3'); + } catch (\Exception $e) { + // Log quietly or handle error + } + } + + // ... mapping methods (mapVerificationStatus, mapDecision, mapMediaType, mapMediaClassification, extractMediaLink) remain as previously defined + private function mapVerificationStatus($legacyStatus): int { return match ((string) $legacyStatus) { @@ -182,33 +326,19 @@ private function extractMediaLink(?string $link): ?string if (empty($link)) { return null; } - - // Find all URLs (starts with http or https) preg_match_all('/(https?:\/\/[^\s]+)/i', $link, $matches); - - // If no URL found, return the original string truncated if (empty($matches[0])) { - return Str::limit($link, 50, ''); + return Str::limit($link, 255, ''); } - - // Try to find the main media website URL foreach ($matches[0] as $url) { - $url = rtrim($url, ','); // clean trailing comma if any - - // Skip common non-media-website URLs if there's an alternative - if (count($matches[0]) > 1 && ( - str_contains($url, 'google.') || - str_contains($url, 'drive.') || - str_contains($url, 'share.') || - str_contains($url, 'bit.ly') - )) { + $url = rtrim($url, ','); + if (count($matches[0]) > 1 && (str_contains($url, 'google.') || str_contains($url, 'drive.') || str_contains($url, 'share.') || str_contains($url, 'bit.ly'))) { continue; } - return Str::limit($url, 50, ''); + return Str::limit($url, 255, ''); } - // Fallback to the first URL found - return Str::limit($matches[0][0], 50, ''); + return Str::limit($matches[0][0], 255, ''); } } diff --git a/app/Console/Commands/MigrateContentRecapCommand.php b/app/Console/Commands/MigrateContentRecapCommand.php index 862ba17..0fd1b70 100644 --- a/app/Console/Commands/MigrateContentRecapCommand.php +++ b/app/Console/Commands/MigrateContentRecapCommand.php @@ -6,8 +6,11 @@ use App\Enums\ContentType; use App\Enums\IsActive; use App\Enums\SocialMedia; +use App\Models\ContentRecap; use Illuminate\Console\Command; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class MigrateContentRecapCommand extends Command @@ -24,72 +27,107 @@ class MigrateContentRecapCommand extends Command * * @var string */ - protected $description = 'Migrate content recap data from legacy database'; + protected $description = 'Migrate content recap data and classifications with S3 images and optimized processing'; + + /** + * Cache for migrated IDs with media to avoid redundant processing. + */ + private array $migratedIds = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting content recap migration...'); + $this->info('Starting optimized content recap migration...'); $legacyConn = DB::connection('mysql_second'); - // 1. Migrate Classifications first - $this->info('Migrating classifications for content recap...'); - $legacyRecapClasses = $legacyConn->table('content_recap_classifications')->get(); - $classMapping = []; + // 1. Migrate Classifications first (Small table, can handle simply) + $this->migrateClassifications(); - foreach ($legacyRecapClasses as $legacyClass) { - // Check by name to avoid duplicates in classifications table - $existing = DB::table('classifications')->where('name', $legacyClass->name)->first(); + // 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(); - if ($existing) { - $classMapping[$legacyClass->id] = $existing->id; - } else { - $newId = DB::table('classifications')->insertGetId([ - '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, - ]); - $classMapping[$legacyClass->id] = $newId; - } - } + // 3. Migrate Content Recaps + $query = $legacyConn->table('content_recap'); + $totalCount = $query->count(); - // 2. Migrate Content Recaps - $this->info('Migrating content recaps...'); - $legacyRecaps = $legacyConn->table('content_recap')->get(); - - if ($legacyRecaps->isEmpty()) { + if ($totalCount === 0) { $this->warn('No content recaps found in legacy database.'); return; } - $this->withProgressBar($legacyRecaps, function ($legacy) use ($classMapping) { - $classificationId = $classMapping[$legacy->classification_id] ?? null; + $this->info("Found {$totalCount} legacy content recap records."); - // Fallback for classification if not found in master table mapping - if (! $classificationId && $legacy->classification) { - $classificationId = DB::table('classifications') - ->where('name', $legacy->classification) - ->value('id'); + $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 still no classification, skip or use a default one? - // The table requires classification_id so we must have one. if (! $classificationId) { - return; // Skip if no classification + $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, 50, ''), + '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), @@ -99,10 +137,68 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Content recap migration completed successfully!'); + // 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 diff --git a/app/Console/Commands/MigrateDepartmentCommand.php b/app/Console/Commands/MigrateDepartmentCommand.php index 127aced..488adaf 100644 --- a/app/Console/Commands/MigrateDepartmentCommand.php +++ b/app/Console/Commands/MigrateDepartmentCommand.php @@ -21,22 +21,56 @@ class MigrateDepartmentCommand extends Command * * @var string */ - protected $description = 'Migrate department data from legacy agencies table'; + protected $description = 'Migrate department data from legacy agencies table with optimized processing'; + + /** + * Store migrated record IDs to optimize processing. + */ + private array $migratedIds = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting department migration...'); + $this->info('Starting optimized department migration...'); $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('agencies'); - // Migrate Departments (Agencies) - $this->info('Migrating departments...'); - $legacyAgencies = $legacyConn->table('agencies')->get(); + $totalCount = $query->count(); + if ($totalCount === 0) { + $this->warn('No departments found in legacy database.'); - $this->withProgressBar($legacyAgencies, function ($legacy) { + return; + } + + $this->info("Found {$totalCount} legacy department records."); + + // Cache existing IDs for faster processing + $this->migratedIds = DB::table('departments')->pluck('id')->flip()->all(); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processDepartment($legacy); + } + }); + + $this->newLine(); + $this->info('Department migration completed successfully!'); + } + + /** + * Process a single legacy department record. + */ + private function processDepartment($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('departments')->updateOrInsert( ['id' => $legacy->id], [ @@ -48,9 +82,10 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } } diff --git a/app/Console/Commands/MigrateIssueManagementCommand.php b/app/Console/Commands/MigrateIssueManagementCommand.php index 01d9517..28776df 100644 --- a/app/Console/Commands/MigrateIssueManagementCommand.php +++ b/app/Console/Commands/MigrateIssueManagementCommand.php @@ -23,27 +23,49 @@ class MigrateIssueManagementCommand extends Command * * @var string */ - protected $description = 'Migrate issue management data from legacy database'; + protected $description = 'Migrate issue management data from legacy database with optimized processing'; /** * Execute the console command. */ public function handle() { - $this->info('Starting issue management migration...'); + $this->info('Starting optimized issue management migration...'); $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('issue_management'); - // Get legacy issue management - $legacyIssues = $legacyConn->table('issue_management')->get(); - - if ($legacyIssues->isEmpty()) { + $totalCount = $query->count(); + if ($totalCount === 0) { $this->warn('No issue management records found in legacy database.'); return; } - $this->withProgressBar($legacyIssues, function ($legacy) { + $this->info("Found {$totalCount} legacy issue management records."); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processIssue($legacy); + } + }); + + $this->newLine(); + $this->info('Issue management migration completed successfully!'); + } + + /** + * Process a single legacy issue management record. + */ + private function processIssue($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->description, 40).'... '); + + try { + // Step 1: Insert/Update main record DB::table('issue_management')->updateOrInsert( ['id' => $legacy->id], [ @@ -61,46 +83,57 @@ public function handle() ] ); - // Handle Departments (Legacy 'agency' field is string, can be multiple separated by comma) - $agencyString = trim($legacy->agency); - if (! empty($agencyString)) { - $agencyNames = explode(',', $agencyString); + // Step 2: Handle Departments pivot mapping + $this->handleDepartments($legacy); - foreach ($agencyNames as $agencyName) { - $agencyName = trim($agencyName); - if (empty($agencyName)) { - continue; - } - - $department = Department::firstOrCreate( - ['name' => $agencyName], - [ - 'alias' => Str::limit($agencyName, 20, ''), - 'is_active' => IsActive::ACTIVE, - ] - ); - - DB::table('department_issue_management')->updateOrInsert( - [ - 'department_id' => $department->id, - 'issue_management_id' => $legacy->id, - ], - [ - 'created_at' => now(), - 'updated_at' => now(), - ] - ); - } - } - }); - $this->newLine(); - - $this->info('Issue management migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } - private function mapSentiment(string $legacyValue): int + /** + * Handle department pivot table mapping. + */ + private function handleDepartments($legacy): void { - return match (strtolower($legacyValue)) { + $agencyString = trim($legacy->agency); + if (empty($agencyString)) { + return; + } + + $agencyNames = array_filter(array_map('trim', explode(',', $agencyString))); + + foreach ($agencyNames as $agencyName) { + // Ensure department exists in master table + $department = Department::firstOrCreate( + ['name' => $agencyName], + [ + 'alias' => Str::limit($agencyName, 20, ''), + 'is_active' => IsActive::ACTIVE, + ] + ); + + // Link to pivot table + DB::table('department_issue_management')->updateOrInsert( + [ + 'department_id' => $department->id, + 'issue_management_id' => $legacy->id, + ], + [ + 'created_at' => now(), + 'updated_at' => now(), + ] + ); + } + } + + /** + * Map sentiment strings to IssueSentiment Enum values. + */ + private function mapSentiment(?string $legacyValue): int + { + return match (strtolower($legacyValue ?? '')) { 'positif' => IssueSentiment::POSITIVE->value, 'negatif' => IssueSentiment::NEGATIVE->value, 'netral' => IssueSentiment::NEUTRAL->value, diff --git a/app/Console/Commands/MigrateJournalistCommand.php b/app/Console/Commands/MigrateJournalistCommand.php index 6c00ad9..aac6fdf 100644 --- a/app/Console/Commands/MigrateJournalistCommand.php +++ b/app/Console/Commands/MigrateJournalistCommand.php @@ -2,8 +2,11 @@ namespace App\Console\Commands; +use App\Models\Journalist; use Illuminate\Console\Command; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class MigrateJournalistCommand extends Command @@ -20,34 +23,70 @@ class MigrateJournalistCommand extends Command * * @var string */ - protected $description = 'Migrate journalists from legacy database'; + protected $description = 'Migrate journalists and their documents from legacy database'; + + /** + * Cache for migrated IDs with media to avoid redundant processing. + */ + private array $migratedIds = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting journalist migration...'); + $this->info('Starting optimized journalist migration with document support...'); $legacyConn = DB::connection('mysql_second'); - $legacyJournalists = $legacyConn->table('journalists')->get(); + $query = $legacyConn->table('journalists'); - if ($legacyJournalists->isEmpty()) { + $totalCount = $query->count(); + if ($totalCount === 0) { $this->warn('No journalists found in legacy database.'); return; } - $this->withProgressBar($legacyJournalists, function ($legacy) { - // Find corresponding partner media (outlet) in the NEW database - // legacy 'media' column is the partner_media_id - $partnerMedia = DB::table('partner_media')->where('id', $legacy->media)->first(); + $this->info("Found {$totalCount} legacy journalist records."); + // Pre-cache migrated IDs + $this->migratedIds = DB::table('media') + ->where('model_type', Journalist::class) + ->where('collection_name', 'journalists') + ->pluck('model_id') + ->flip() + ->all(); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processJournalist($legacy); + } + }); + + $this->newLine(); + $this->info('Journalist migration completed successfully!'); + } + + /** + * Process a single legacy journalist record. + */ + private function processJournalist($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->name, 40).'... '); + + try { + // Find corresponding partner media + $partnerMedia = DB::table('partner_media')->where('id', $legacy->media)->first(); if (! $partnerMedia) { - // If partner media doesn't exist, we skip as it's required + $this->output->writeln('OUTLET NOT FOUND'); + return; } + // 1. Update/Insert Journalist DB::table('journalists')->updateOrInsert( ['id' => $legacy->id], [ @@ -62,9 +101,82 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Journalist migration completed successfully!'); + // 2. Media Documents Migration (Skip if already has media) + if (isset($this->migratedIds[$legacy->id])) { + $this->output->writeln('DONE (SKIP DOCS)'); + + return; + } + + $journalist = Journalist::withTrashed()->find($legacy->id); + if ($journalist) { + $this->migrateJournalistDocuments($journalist, $legacy); + } + + $this->output->writeln('DONE + DOCS'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } + } + + /** + * Migrate journalist documents from S3. + */ + private function migrateJournalistDocuments(Journalist $journalist, $legacy): void + { + $docMapping = [ + 'doc_of_press_card' => 'press-card', + 'doc_of_certificate' => 'ukw-certificate', + ]; + + foreach ($docMapping as $legacyField => $docType) { + $legacyPath = trim($legacy->{$legacyField} ?? ''); + if (empty($legacyPath) || $legacyPath === '-' || $legacyPath === 'no-image.png') { + continue; + } + + // Path mapping: old data/companies/ + path from db (based on legacy structure) + $sourcePath = 'old data/companies/'.$legacyPath; + + $this->importOneDocument($journalist, $sourcePath, $docType, $legacy); + } + } + + /** + * Import a single document to Media Library. + */ + private function importOneDocument(Journalist $journalist, string $sourcePath, string $docType, $legacy): void + { + $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) { + return; + } + + try { + $journalist->addMediaFromDisk($sourcePath, 's3') + ->withCustomProperties([ + 'feature' => 'journalists', + 'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(), + 'doc_type' => $docType, + ]) + ->preservingOriginal() + ->toMediaCollection('journalists', 's3'); + } catch (\Exception $e) { + // Log quietly + } } } diff --git a/app/Console/Commands/MigrateLocationCommand.php b/app/Console/Commands/MigrateLocationCommand.php index 12d03fc..77b25f3 100644 --- a/app/Console/Commands/MigrateLocationCommand.php +++ b/app/Console/Commands/MigrateLocationCommand.php @@ -5,6 +5,7 @@ use App\Enums\IsActive; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class MigrateLocationCommand extends Command { @@ -20,54 +21,113 @@ class MigrateLocationCommand extends Command * * @var string */ - protected $description = 'Migrate location data from legacy database'; + protected $description = 'Migrate location and sub-location data from legacy database with optimized processing'; /** * Execute the console command. */ public function handle() { - $this->info('Starting location migration...'); + $this->info('Starting optimized location migration...'); + $this->migrateLocations(); + $this->newLine(); + $this->migrateSubLocations(); + + $this->newLine(); + $this->info('Location migration completed successfully!'); + } + + /** + * Migrate main locations. + */ + private function migrateLocations(): void + { $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('locuses'); - // Migrate Locations (Locuses) - $this->info('Migrating locations...'); - $legacyLocations = $legacyConn->table('locuses')->get(); + $totalCount = $query->count(); + $this->info("Found {$totalCount} legacy location records."); - $this->withProgressBar($legacyLocations, function ($legacy) { + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processLocation($legacy); + } + }); + } + + /** + * Process a single legacy location record. + */ + private function processLocation($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} (LOC) Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('locations')->updateOrInsert( ['id' => $legacy->id], [ 'name' => $legacy->name, 'is_active' => $legacy->status == '1' ? IsActive::ACTIVE->value : IsActive::INACTIVE->value, - 'created_at' => $legacy->created_at, - 'updated_at' => $legacy->updated_at, + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); + + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } + } + + /** + * Migrate sub-locations. + */ + private function migrateSubLocations(): void + { + $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('sub_locuses'); + + $totalCount = $query->count(); + $this->info("Found {$totalCount} legacy sub-location records."); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processSubLocation($legacy); + } }); - $this->newLine(); + } - // Migrate Sub Locations (Sub Locuses) - $this->info('Migrating sub locations...'); - $legacySubLocations = $legacyConn->table('sub_locuses')->get(); + /** + * Process a single legacy sub-location record. + */ + private function processSubLocation($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; - $this->withProgressBar($legacySubLocations, function ($legacy) { + $this->output->write("{$idOutput} (SUB) Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('sub_locations')->updateOrInsert( ['id' => $legacy->id], [ - 'location_id' => $legacy->locus, + 'location_id' => $legacy->locus, // Foreign key to locations 'name' => $legacy->name, 'is_active' => $legacy->status == '1' ? IsActive::ACTIVE->value : IsActive::INACTIVE->value, - 'created_at' => $legacy->created_at, - 'updated_at' => $legacy->updated_at, + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } } diff --git a/app/Console/Commands/MigrateMediaMonitoringCommand.php b/app/Console/Commands/MigrateMediaMonitoringCommand.php index 93f9286..51d6eed 100644 --- a/app/Console/Commands/MigrateMediaMonitoringCommand.php +++ b/app/Console/Commands/MigrateMediaMonitoringCommand.php @@ -4,8 +4,11 @@ use App\Enums\Channel; use App\Enums\IsActive; +use App\Models\MediaMonitoring; use Illuminate\Console\Command; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; class MigrateMediaMonitoringCommand extends Command @@ -22,81 +25,194 @@ class MigrateMediaMonitoringCommand extends Command * * @var string */ - protected $description = 'Migrate media monitoring data from legacy database'; + protected $description = 'Migrate media monitoring data with S3 images and optimized processing'; + + /** + * Cache for migrated IDs to prevent redundant processing. + */ + private array $migratedIds = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting media monitoring migration...'); + $this->info('Starting optimized media monitoring migration...'); $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('media_monitorings'); - // Get legacy monitorings - $legacyMonitorings = $legacyConn->table('media_monitorings')->get(); - - if ($legacyMonitorings->isEmpty()) { - $this->warn('No media monitorings found in legacy database.'); + $totalCount = $query->count(); + if ($totalCount === 0) { + $this->warn('No media monitoring found in legacy database.'); return; } - $this->withProgressBar($legacyMonitorings, function ($legacy) { - // Main record - 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, - ] - ); + $this->info("Found {$totalCount} legacy media monitoring records."); - // Handle Theme (Insert into themes table if not exists) - $themeName = trim($legacy->theme); - if (! empty($themeName)) { - $themeName = Str::limit($themeName, 50, ''); + // 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(); - // Ensure theme exists in master table - 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'); - - // Link to pivot table - if ($themeId) { - DB::table('media_monitoring_theme')->updateOrInsert( - [ - 'media_monitoring_id' => $legacy->id, - 'theme_id' => $themeId, - ] - ); - } + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processMonitoring($legacy); } }); - $this->newLine(); + $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 ?? '')) { diff --git a/app/Console/Commands/MigrateThemeCommand.php b/app/Console/Commands/MigrateThemeCommand.php index 5f4fd58..d5b85a2 100644 --- a/app/Console/Commands/MigrateThemeCommand.php +++ b/app/Console/Commands/MigrateThemeCommand.php @@ -5,6 +5,7 @@ use App\Enums\IsActive; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class MigrateThemeCommand extends Command { @@ -20,27 +21,48 @@ class MigrateThemeCommand extends Command * * @var string */ - protected $description = 'Migrate theme data from legacy proof_airing_themes table'; + protected $description = 'Migrate theme data from legacy proof_airing_themes table with optimized processing'; /** * Execute the console command. */ public function handle() { - $this->info('Starting theme migration...'); + $this->info('Starting optimized theme migration...'); $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('proof_airing_themes'); - // Get legacy themes - $legacyThemes = $legacyConn->table('proof_airing_themes')->get(); - - if ($legacyThemes->isEmpty()) { + $totalCount = $query->count(); + if ($totalCount === 0) { $this->warn('No themes found in legacy database.'); return; } - $this->withProgressBar($legacyThemes, function ($legacy) { + $this->info("Found {$totalCount} legacy theme records."); + + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processTheme($legacy); + } + }); + + $this->newLine(); + $this->info('Theme migration completed successfully!'); + } + + /** + * Process a single legacy theme record. + */ + private function processTheme($legacy): void + { + $idText = "[#{$legacy->id}]"; + $idOutput = $legacy->deleted_at ? "{$idText}" : "{$idText}"; + + $this->output->write("{$idOutput} Processing: ".Str::limit($legacy->name, 40).'... '); + + try { DB::table('themes')->updateOrInsert( ['id' => $legacy->id], [ @@ -51,9 +73,10 @@ public function handle() 'deleted_at' => $legacy->deleted_at, ] ); - }); - $this->newLine(); - $this->info('Theme migration completed successfully!'); + $this->output->writeln('DONE'); + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } } diff --git a/app/Console/Commands/MigrateUserCommand.php b/app/Console/Commands/MigrateUserCommand.php index 1340369..a553124 100644 --- a/app/Console/Commands/MigrateUserCommand.php +++ b/app/Console/Commands/MigrateUserCommand.php @@ -24,53 +24,89 @@ class MigrateUserCommand extends Command * * @var string */ - protected $description = 'Migrate users from legacy database'; + protected $description = 'Migrate users from legacy database with optimized processing and detailed output'; + + /** + * Cache for existing user IDs and emails to prevent redundant queries. + */ + private array $existingUserIds = []; + + private array $existingUserEmails = []; /** * Execute the console command. */ public function handle() { - $this->info('Starting user migration...'); + $this->info('Starting optimized user migration...'); - $legacyUsers = DB::connection('mysql_second')->table('users')->where('deleted_at', null)->get(); + $legacyConn = DB::connection('mysql_second'); + $query = $legacyConn->table('users')->whereNull('deleted_at'); - if ($legacyUsers->isEmpty()) { + $totalCount = $query->count(); + if ($totalCount === 0) { $this->warn('No users found in legacy database.'); return; } - foreach ($legacyUsers as $legacy) { - $existingById = User::where('id', $legacy->id)->first(); - $existingByEmail = User::where('email', $legacy->email)->first(); + $this->info("Found {$totalCount} legacy user records."); - $roleName = $this->mapRole($legacy->role); + // Pre-cache existing data to speed up individual checks + $this->existingUserIds = User::pluck('id')->flip()->all(); + $this->existingUserEmails = User::pluck('email')->flip()->all(); - if (! $existingById && ! $existingByEmail) { + $query->orderBy('id')->chunk(50, function ($chunk) { + foreach ($chunk as $legacy) { + $this->processUser($legacy); + } + }); - $username = $this->generateUniqueUsername($legacy->name, $legacy->email); + $this->newLine(); + $this->info('User migration completed successfully!'); + } - $userId = DB::table('users')->insertGetId([ - 'id' => $legacy->id, - 'name' => Str::limit($legacy->name, 100), - 'email' => $legacy->email, - 'username' => $username, - 'password' => $legacy->password, - 'is_active' => $legacy->status == '2' - ? IsActive::INACTIVE->value - : IsActive::ACTIVE->value, - 'email_verified_at' => now(), - 'created_at' => $legacy->created_at ?? now(), - 'updated_at' => $legacy->updated_at ?? now(), - ]); + /** + * Process a single legacy user record. + */ + private function processUser($legacy): void + { + $this->output->write("[#{$legacy->id}] Processing: ".Str::limit($legacy->name, 40).'... '); - $user = User::find($userId); + // Check for duplicates by ID or Email + if (isset($this->existingUserIds[$legacy->id]) || isset($this->existingUserEmails[$legacy->email])) { + $this->output->writeln('SKIP (Already Exists)'); - if ($user && $roleName) { + return; + } + + try { + $username = $this->generateUniqueUsername($legacy->name, $legacy->email); + + $id = DB::table('users')->insertGetId([ + 'id' => $legacy->id, + 'name' => Str::limit($legacy->name, 100), + 'email' => $legacy->email, + 'username' => $username, + 'password' => $legacy->password, + 'is_active' => $legacy->status == '2' + ? IsActive::INACTIVE->value + : IsActive::ACTIVE->value, + 'email_verified_at' => now(), + 'created_at' => $legacy->created_at ?? now(), + 'updated_at' => $legacy->updated_at ?? now(), + ]); + + $user = User::find($id); + + if ($user) { + // Assign Role + $roleName = $this->mapRole($legacy->role); + if ($roleName) { $user->syncRoles([$roleName]); } + // Initialize User Settings if missing if (! $user->settings()->exists()) { $user->settings()->create([ 'notif_style' => NotifStyle::FORMAL, @@ -81,18 +117,21 @@ public function handle() 'top_navigation' => false, ]); } - } else { - $this->info('User already exists: '.$legacy->email); - } - } - $this->newLine(); - $this->info('User migration completed successfully!'); + $this->output->writeln('DONE'); + } else { + $this->output->writeln('FAILED TO RETRIEVE'); + } + } catch (\Exception $e) { + $this->output->writeln('FAILED: '.$e->getMessage().''); + } } - private function generateUniqueUsername($name, $email) + /** + * Generate a unique username based on name or email. + */ + private function generateUniqueUsername($name, $email): string { - // Try name first $base = Str::limit(Str::slug($name, ''), 10, ''); if (empty($base)) { $base = Str::limit(explode('@', $email)[0], 10, ''); @@ -101,7 +140,6 @@ private function generateUniqueUsername($name, $email) $username = $base; $counter = 1; - // Ensure unique within 10 chars while (User::where('username', $username)->exists()) { $suffix = (string) $counter; $maxBaseLength = 10 - strlen($suffix); @@ -110,12 +148,15 @@ private function generateUniqueUsername($name, $email) if ($counter > 999) { break; - } // Safety + } } return $username; } + /** + * Map legacy role IDs to RoleEnum. + */ private function mapRole($legacyRole): ?string { return match ((int) $legacyRole) { diff --git a/app/Filament/Resources/Monitoring/ContentRecaps/Schemas/ContentRecapForm.php b/app/Filament/Resources/Monitoring/ContentRecaps/Schemas/ContentRecapForm.php index b6c2432..dd724ac 100644 --- a/app/Filament/Resources/Monitoring/ContentRecaps/Schemas/ContentRecapForm.php +++ b/app/Filament/Resources/Monitoring/ContentRecaps/Schemas/ContentRecapForm.php @@ -92,7 +92,7 @@ public static function configure(Schema $schema): Schema ->disk(config('filesystems.default')) ->acceptedFileTypes(['image/*']) ->maxSize(1024 * 3) - ->collection('content-recpaps') + ->collection('content-recaps') ->customProperties(fn (): array => [ 'feature' => 'content-recaps', 'date' => now()->toDateString(),