option('chunk')); $dryRun = (bool) $this->option('dry-run'); $legacyConn = $this->option('legacy-connection') ?: null; $table = (string) $this->option('legacy-table'); $this->info('Nova Stories — legacy interview migration'); $this->info("Table: {$table} | Chunk: {$chunk} | Dry-run: " . ($dryRun ? 'YES' : 'NO')); $this->newLine(); try { $db = $legacyConn ? DB::connection($legacyConn) : DB::connection(); // Quick existence check $db->table($table)->limit(1)->get(); } catch (Throwable $e) { $this->error("Cannot access table `{$table}`: " . $e->getMessage()); return self::FAILURE; } $inserted = 0; $skipped = 0; $failed = 0; $db->table($table)->orderBy('id')->chunkById($chunk, function ($rows) use ( $dryRun, &$inserted, &$skipped, &$failed ) { foreach ($rows as $row) { $legacyId = (int) ($row->id ?? 0); if (! $legacyId) { $skipped++; continue; } // Idempotency: skip if already migrated if (Story::where('legacy_interview_id', $legacyId)->exists()) { $skipped++; continue; } try { // ── Resolve / create author ────────────────────────────── $authorName = $this->coerceString($row->username ?? $row->author ?? $row->uname ?? ''); $authorAvatar = $this->coerceString($row->icon ?? $row->avatar ?? ''); $author = null; if ($authorName) { $author = StoryAuthor::firstOrCreate( ['name' => $authorName], ['avatar' => $authorAvatar ?: null] ); } // ── Build slug ─────────────────────────────────────────── $rawTitle = $this->coerceString( $row->headline ?? $row->title ?? $row->subject ?? '' ) ?: 'interview-' . $legacyId; $slugBase = Str::slug(Str::limit($rawTitle, 180)); $slug = $slugBase ?: 'interview-' . $legacyId; // Ensure uniqueness $slug = $this->uniqueSlug($slug); // ── Excerpt ────────────────────────────────────────────── $fullContent = $this->coerceString( $row->content ?? $row->tekst ?? $row->body ?? $row->text ?? '' ); $excerpt = $this->coerceString($row->excerpt ?? $row->intro ?? $row->lead ?? ''); if (! $excerpt && $fullContent) { $excerpt = Str::limit(strip_tags($fullContent), 200); } // ── Cover image ────────────────────────────────────────── $coverRaw = $this->coerceString($row->pic ?? $row->image ?? $row->cover ?? $row->photo ?? ''); $coverImage = $coverRaw ? 'legacy/interviews/' . ltrim($coverRaw, '/') : null; // ── Published date ─────────────────────────────────────── $publishedAt = null; foreach (['datum', 'published_at', 'date', 'created_at'] as $field) { $val = $row->{$field} ?? null; if ($val) { $ts = strtotime((string) $val); if ($ts) { $publishedAt = date('Y-m-d H:i:s', $ts); break; } } } if ($dryRun) { $this->line(" [DRY-RUN] Would import: #{$legacyId} → {$slug}"); $inserted++; continue; } Story::create([ 'slug' => $slug, 'title' => Str::limit($rawTitle, 255), 'excerpt' => $excerpt ?: null, 'content' => $fullContent ?: null, 'cover_image' => $coverImage, 'author_id' => $author?->id, 'views' => max(0, (int) ($row->views ?? $row->hits ?? 0)), 'featured' => false, 'status' => 'published', 'published_at' => $publishedAt, 'legacy_interview_id' => $legacyId, ]); $this->line(" Imported: #{$legacyId} → {$slug}"); $inserted++; } catch (Throwable $e) { $failed++; $this->warn(" FAILED #{$legacyId}: " . $e->getMessage()); Log::warning("stories:migrate-legacy failed for id={$legacyId}", ['error' => $e->getMessage()]); } } }); $this->newLine(); $this->info("Migration complete."); $this->table( ['Inserted', 'Skipped (existing)', 'Failed'], [[$inserted, $skipped, $failed]] ); return $failed > 0 ? self::FAILURE : self::SUCCESS; } // ── Helpers ─────────────────────────────────────────────────────────────── private function coerceString(mixed $value, string $default = ''): string { if ($value === null) { return $default; } $str = trim((string) $value); return $str !== '' ? $str : $default; } /** * Ensure the slug is unique, appending a numeric suffix if needed. */ private function uniqueSlug(string $slug): string { if (! Story::where('slug', $slug)->exists()) { return $slug; } $i = 2; do { $candidate = $slug . '-' . $i++; } while (Story::where('slug', $candidate)->exists()); return $candidate; } }