command->error('course_blocks_data.json bulunamadı. Önce Python parse script çalıştırın.'); return; } /** @var array> $data */ $data = json_decode(file_get_contents($jsonPath), true); $cats = Category::pluck('id', 'slug'); $allCourses = Course::all(); $matched = 0; $created = 0; $newCourses = 0; $blockCount = 0; foreach ($data as $item) { $course = $this->findCourse($allCourses, $item); if (! $course) { // Create new course from article data $categoryId = $cats[$item['category_slug']] ?? null; if (! $categoryId) { $this->command->warn("Kategori bulunamadı: {$item['category_slug']} -> {$item['title_short']}"); continue; } $course = Course::create([ 'category_id' => $categoryId, 'slug' => Str::slug($item['title_short']), 'title' => $item['title_short'], 'desc' => Str::limit($item['long_desc'], 200), 'long_desc' => $item['long_desc'], 'duration' => '', 'requirements' => $item['requirements'], 'scope' => $item['scope'], 'standard' => 'STCW / IMO Uyumlu', 'language' => 'Türkçe', 'location' => 'Kadıköy, İstanbul', 'meta_title' => $item['title_short'].' | Boğaziçi Denizcilik', 'meta_description' => Str::limit($item['long_desc'], 155), ]); $newCourses++; $allCourses->push($course); } else { $matched++; $this->updateCourseFields($course, $item); } // Sync blocks $count = $this->syncBlocks($course, $item['blocks']); $blockCount += $count; } $total = count($data); $this->command->info("Toplam: {$total} makale, {$matched} eşleşme, {$newCourses} yeni kurs, {$blockCount} blok."); } /** * @param Collection $allCourses * @param array $item */ private function findCourse($allCourses, array $item): ?Course { $articleSlug = Str::slug($item['title_short']); $titleNorm = $this->normalize($item['title_short']); // 1. Exact slug match $found = $allCourses->first(fn (Course $c) => $c->slug === $articleSlug); if ($found) { return $found; } // 2. Normalized title contains match $found = $allCourses->first(fn (Course $c) => $this->normalize($c->title) === $titleNorm); if ($found) { return $found; } // 3. Fuzzy — first 25 chars of normalized title $prefix = mb_substr($titleNorm, 0, 25); return $allCourses->first(fn (Course $c) => str_starts_with($this->normalize($c->title), $prefix)); } /** * Normalize text: lowercase, strip accents, collapse whitespace. */ private function normalize(string $text): string { $text = mb_strtolower($text); // Turkish specific replacements $text = str_replace( ['ç', 'ğ', 'ı', 'ö', 'ş', 'ü', 'â', 'î', 'û'], ['c', 'g', 'i', 'o', 's', 'u', 'a', 'i', 'u'], $text, ); $text = preg_replace('/[^a-z0-9\s]/', '', $text); return preg_replace('/\s+/', ' ', trim($text)); } /** * @param array $item */ private function updateCourseFields(Course $course, array $item): void { $updates = []; if (empty($course->long_desc) && ! empty($item['long_desc'])) { $updates['long_desc'] = $item['long_desc']; } if ((empty($course->requirements) || $course->requirements === []) && ! empty($item['requirements'])) { $updates['requirements'] = $item['requirements']; } if ((empty($course->scope) || $course->scope === []) && ! empty($item['scope'])) { $updates['scope'] = $item['scope']; } if (! empty($updates)) { $course->update($updates); } } /** * @param array> $blocks */ private function syncBlocks(Course $course, array $blocks): int { $count = 0; foreach ($blocks as $block) { CourseBlock::updateOrCreate( ['course_id' => $course->id, 'order_index' => $block['order_index']], ['type' => $block['type'], 'content' => $block['content']], ); $count++; } return $count; } }