feat: Implement semantic video naming, MySQL database curriculum synchronization, and live two-way binding between filesystem and DB
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../app/Core/Database.php';
|
||||
require_once __DIR__ . '/../app/Core/Security.php';
|
||||
require_once __DIR__ . '/../app/Services/CurriculumService.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\CurriculumService;
|
||||
|
||||
echo "=== SAQEL ENTERPRISE CURRICULUM DATABASE SYNC ===\n";
|
||||
|
||||
$manifestFile = __DIR__ . '/../storage/curriculum/manifest.json';
|
||||
if (!file_exists($manifestFile)) {
|
||||
die("Error: manifest.json not found at $manifestFile\n");
|
||||
}
|
||||
|
||||
$manifest = json_decode(file_get_contents($manifestFile), true);
|
||||
if (!$manifest) {
|
||||
die("Error: Failed to parse manifest.json\n");
|
||||
}
|
||||
|
||||
// 1. Ensure Default Admin/Official Teacher exists for curriculum courses
|
||||
$teacher = Database::selectOne("SELECT id, uuid FROM teachers LIMIT 1");
|
||||
if (!$teacher) {
|
||||
echo "Creating Default Official Curriculum Teacher...\n";
|
||||
$identUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0x0fff)|0x4000,mt_rand(0,0x3fff)|0x8000,mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff));
|
||||
$identId = Database::insert(
|
||||
"INSERT INTO auth_identities (uuid, phone_number, phone_hash, status) VALUES (?, ?, ?, 'active')",
|
||||
[$identUuid, \App\Core\Security::encrypt('962790000000'), \App\Core\Security::blindIndex('962790000000')]
|
||||
);
|
||||
$teachUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0x0fff)|0x4000,mt_rand(0,0x3fff)|0x8000,mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff));
|
||||
$teacherId = Database::insert(
|
||||
"INSERT INTO teachers (uuid, identity_id, full_name, specialization, bio) VALUES (?, ?, 'فريق صَقِل للمناهج الرسمية', 'المناهج الوزارية المطورة', 'الجهة الرسمية لإعداد وفهرسة المناهج الوزارية المطورة لوزارة التربية والتعليم')",
|
||||
[$teachUuid, $identId]
|
||||
);
|
||||
$teacher = ['id' => $teacherId, 'uuid' => $teachUuid];
|
||||
}
|
||||
$teacherId = (int)$teacher['id'];
|
||||
|
||||
// 2. Iterate through Grades and Subjects in manifest
|
||||
foreach ($manifest as $gradeKey => $grade) {
|
||||
$gradeName = $grade['name'] ?? $gradeKey;
|
||||
|
||||
foreach ($grade['subjects'] as $subjectKey => $subjectData) {
|
||||
$subjectName = $subjectData['name'] ?? $subjectKey;
|
||||
$stream = 'scientific';
|
||||
if (str_contains($subjectKey, 'english')) {
|
||||
$stream = 'common';
|
||||
}
|
||||
|
||||
// A. Insert/Update Subject
|
||||
$dbSubject = Database::selectOne("SELECT id FROM subjects WHERE code = ? LIMIT 1", [$subjectKey]);
|
||||
if (!$dbSubject) {
|
||||
$subId = Database::insert(
|
||||
"INSERT INTO subjects (name, code, stream, is_active) VALUES (?, ?, ?, 1)",
|
||||
[$subjectName, $subjectKey, $stream]
|
||||
);
|
||||
echo "✅ Created Subject: {$subjectName} (code: {$subjectKey})\n";
|
||||
} else {
|
||||
$subId = (int)$dbSubject['id'];
|
||||
Database::query("UPDATE subjects SET name = ? WHERE id = ?", [$subjectName, $subId]);
|
||||
}
|
||||
|
||||
// B. Insert/Update Courses for each Semester
|
||||
foreach ($subjectData['semesters'] as $semesterKey => $semesterData) {
|
||||
$semType = ($semesterKey === 'semester_2') ? 'second' : 'first';
|
||||
$courseTitle = "{$subjectName} — {$gradeName} (" . ($semType === 'first' ? 'الفصل الأول' : 'الفصل الثاني') . ")";
|
||||
|
||||
$dbCourse = Database::selectOne("SELECT id FROM courses WHERE subject_id = ? AND semester = ? LIMIT 1", [$subId, $semType]);
|
||||
if (!$dbCourse) {
|
||||
$cUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0x0fff)|0x4000,mt_rand(0,0x3fff)|0x8000,mt_rand(0,0xffff),mt_rand(0,0xffff),mt_rand(0,0xffff));
|
||||
$courseId = Database::insert(
|
||||
"INSERT INTO courses (uuid, subject_id, teacher_id, title, description, semester, price_jod, is_published)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0.00, 1)",
|
||||
[$cUuid, $subId, $teacherId, $courseTitle, "المساق الوزاري الرسمي المعتمد والمفرغ تفاعلياً لمنهاج {$subjectName}", $semType]
|
||||
);
|
||||
echo " 📚 Created Course: {$courseTitle} (ID: {$courseId})\n";
|
||||
} else {
|
||||
$courseId = (int)$dbCourse['id'];
|
||||
Database::query("UPDATE courses SET title = ? WHERE id = ?", [$courseTitle, $courseId]);
|
||||
}
|
||||
|
||||
// C. Insert/Update Lessons under each Unit
|
||||
$seqOrder = 1;
|
||||
foreach ($semesterData['units'] as $unitKey => $unitData) {
|
||||
$unitName = $unitData['name'] ?? $unitKey;
|
||||
|
||||
foreach ($unitData['lessons'] as $les) {
|
||||
$lessonTitle = $les['title'];
|
||||
$filePath = $les['file'];
|
||||
$fullDiskPath = __DIR__ . '/../storage/curriculum/' . $filePath;
|
||||
|
||||
$mdContent = '';
|
||||
if (file_exists($fullDiskPath)) {
|
||||
$mdContent = file_get_contents($fullDiskPath);
|
||||
}
|
||||
|
||||
$aiVideoUrl = $les['ai_video_url'] ?? null;
|
||||
$cheatSheet = $les['cheat_sheet'] ?? null;
|
||||
$socraticQuiz = isset($les['socratic_quiz']) ? json_encode($les['socratic_quiz'], JSON_UNESCAPED_UNICODE) : null;
|
||||
|
||||
// Match existing lesson by course_id and title or file path
|
||||
$dbLesson = Database::selectOne(
|
||||
"SELECT id FROM lessons WHERE course_id = ? AND title = ? LIMIT 1",
|
||||
[$courseId, $lessonTitle]
|
||||
);
|
||||
|
||||
if (!$dbLesson) {
|
||||
$lesId = Database::insert(
|
||||
"INSERT INTO lessons (course_id, title, sequence_order, ai_video_url, markdown_content, cheat_sheet_markdown, socratic_quiz_json, is_free_preview)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 1)",
|
||||
[$courseId, $lessonTitle, $seqOrder, $aiVideoUrl, $mdContent, $cheatSheet, $socraticQuiz]
|
||||
);
|
||||
echo " 📄 [NEW] Lesson inserted: {$lessonTitle} (ID: {$lesId})\n";
|
||||
} else {
|
||||
$lesId = (int)$dbLesson['id'];
|
||||
Database::query(
|
||||
"UPDATE lessons SET sequence_order = ?, markdown_content = ?, ai_video_url = COALESCE(?, ai_video_url), cheat_sheet_markdown = COALESCE(?, cheat_sheet_markdown), socratic_quiz_json = COALESCE(?, socratic_quiz_json) WHERE id = ?",
|
||||
[$seqOrder, $mdContent, $aiVideoUrl, $cheatSheet, $socraticQuiz, $lesId]
|
||||
);
|
||||
echo " 🔄 [UPDATED] Lesson: {$lessonTitle} (ID: {$lesId})\n";
|
||||
}
|
||||
$seqOrder++;
|
||||
}
|
||||
}
|
||||
|
||||
// D. Insert Resources (Books, Activity Books)
|
||||
if (!empty($semesterData['resources'])) {
|
||||
foreach ($semesterData['resources'] as $resGroupKey => $resGroup) {
|
||||
foreach ($resGroup['items'] as $resItem) {
|
||||
$resTitle = $resItem['title'];
|
||||
$resFilePath = $resItem['file'];
|
||||
$resDiskPath = __DIR__ . '/../storage/curriculum/' . $resFilePath;
|
||||
$resMd = file_exists($resDiskPath) ? file_get_contents($resDiskPath) : '';
|
||||
|
||||
// Add as special lesson / resource entry
|
||||
$dbResLesson = Database::selectOne(
|
||||
"SELECT id FROM lessons WHERE course_id = ? AND title = ? LIMIT 1",
|
||||
[$courseId, $resTitle]
|
||||
);
|
||||
if (!$dbResLesson) {
|
||||
Database::insert(
|
||||
"INSERT INTO lessons (course_id, title, sequence_order, markdown_content, is_free_preview)
|
||||
VALUES (?, ?, ?, ?, 1)",
|
||||
[$courseId, $resTitle, $seqOrder, $resMd]
|
||||
);
|
||||
echo " 📎 [RESOURCE] Added Resource: {$resTitle}\n";
|
||||
}
|
||||
$seqOrder++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n🎉 SUCCESS! All curriculum subjects, courses, and lessons are synchronized with MySQL database!\n";
|
||||
Reference in New Issue
Block a user