Files
bogazici-api/database/seeders/CourseContentSeeder.php
2026-03-27 10:41:54 +03:00

170 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Database\Seeders;
use App\Models\Category;
use App\Models\Course;
use App\Models\CourseBlock;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
class CourseContentSeeder extends Seeder
{
/**
* Seed course blocks (rich content) from parsed docx articles.
* Updates long_desc/requirements/scope on existing courses if empty.
* Creates new courses from articles that don't match existing ones.
*
* Source: storage/app/course_blocks_data.json
* Run AFTER CourseSeeder. Existing CourseSeeder is untouched.
*/
public function run(): void
{
$jsonPath = storage_path('app/course_blocks_data.json');
if (! file_exists($jsonPath)) {
$this->command->error('course_blocks_data.json bulunamadı. Önce Python parse script çalıştırın.');
return;
}
/** @var array<int, array<string, mixed>> $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<int, Course> $allCourses
* @param array<string, mixed> $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<string, mixed> $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<int, array<string, mixed>> $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;
}
}