From 739f934a265e8441e3fc165c8fdc4749b40b4b40 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Wed, 2 Sep 2026 07:43:58 +0300 Subject: [PATCH] Update Saqel Platform: 2026-09-02 07:43:58 --- .../app/Controllers/CurriculumController.php | 40 +++++++++++-------- backend/app/Services/CurriculumService.php | 19 +++++++++ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/backend/app/Controllers/CurriculumController.php b/backend/app/Controllers/CurriculumController.php index c3ecaa9..d48c67d 100644 --- a/backend/app/Controllers/CurriculumController.php +++ b/backend/app/Controllers/CurriculumController.php @@ -264,29 +264,35 @@ class CurriculumController // Resolve a valid FK-safe course_id for the master ministry curriculum. // IMPORTANT: course_id = 0 causes SQLSTATE[23000] FK violation because // the `lessons` table enforces courses(id) ON DELETE CASCADE. - $courseId = CurriculumService::getOrCreateSystemCourse(); - $title = basename($file, '.md'); + $manifestLesson = CurriculumService::findLessonByFile($file); + $title = $manifestLesson['title'] ?? basename($file, '.md'); + $existingLesson = \App\Core\Database::selectOne( + "SELECT id, course_id FROM lessons WHERE title = ? OR markdown_content LIKE ? LIMIT 1", + [$title, '%' . $file . '%'] + ); + $courseId = $existingLesson + ? (int)$existingLesson['course_id'] + : CurriculumService::getOrCreateSystemCourse(); // 1. Upload & Transcode to HLS & Push to R2 (handled by VideoService) $uploadResult = \App\Services\VideoService::handleDirectUpload($_FILES['video_file'], $courseId, $title); $videoUrl = $uploadResult['r2_url'] ?? $uploadResult['hls_url']; - // 2. Insert into lessons table as AI-generated ministry version - $lessonId = \App\Core\Database::insert( - "INSERT INTO lessons (course_id, title, sequence_order, storage_type, video_uuid, bunny_video_id, local_path, hls_url, thumbnail_url, duration_seconds, is_free_preview, encoding_status, ai_video_url) - VALUES (?, ?, 1, 'api_upload', ?, '', ?, ?, ?, ?, 0, 'ready', ?)", - [ - $courseId, - $title, - $uploadResult['video_uuid'], - $uploadResult['local_path'], - $uploadResult['hls_url'], - $uploadResult['thumbnail_url'], - $uploadResult['duration'] ?? 0, - $videoUrl - ] - ); + // 2. Bind the upload to the synchronized Manifest lesson when present. + if ($existingLesson) { + $lessonId = (int)$existingLesson['id']; + \App\Core\Database::query( + "UPDATE lessons SET storage_type = 'api_upload', video_uuid = ?, bunny_video_id = '', local_path = ?, hls_url = ?, thumbnail_url = ?, duration_seconds = ?, encoding_status = 'ready', ai_video_url = ? WHERE id = ?", + [$uploadResult['video_uuid'], $uploadResult['local_path'], $uploadResult['hls_url'], $uploadResult['thumbnail_url'], $uploadResult['duration'] ?? 0, $videoUrl, $lessonId] + ); + } else { + $lessonId = \App\Core\Database::insert( + "INSERT INTO lessons (course_id, title, sequence_order, storage_type, video_uuid, bunny_video_id, local_path, hls_url, thumbnail_url, duration_seconds, is_free_preview, encoding_status, ai_video_url) + VALUES (?, ?, 1, 'api_upload', ?, '', ?, ?, ?, ?, 0, 'ready', ?)", + [$courseId, $title, $uploadResult['video_uuid'], $uploadResult['local_path'], $uploadResult['hls_url'], $uploadResult['thumbnail_url'], $uploadResult['duration'] ?? 0, $videoUrl] + ); + } // 3. Trigger Autonomous Zero-Touch AI Analysis & Socratic Checkpoint Generation \App\Services\AiVideoAnalyzerService::processLessonAutonomously($lessonId); diff --git a/backend/app/Services/CurriculumService.php b/backend/app/Services/CurriculumService.php index 05db445..f396ff6 100644 --- a/backend/app/Services/CurriculumService.php +++ b/backend/app/Services/CurriculumService.php @@ -211,6 +211,25 @@ class CurriculumService return $walk(self::getCurriculumTree()); } + /** Resolve a manifest file path to its canonical lesson record. */ + public static function findLessonByFile(string $file): ?array + { + $walk = function ($node) use (&$walk, $file): ?array { + if (!is_array($node)) return null; + if (isset($node['lessons']) && is_array($node['lessons'])) { + foreach ($node['lessons'] as $lesson) { + if (is_array($lesson) && (string)($lesson['file'] ?? '') === $file) return $lesson; + } + } + foreach ($node as $value) { + $found = $walk($value); + if ($found !== null) return $found; + } + return null; + }; + return $walk(self::getCurriculumTree()); + } + /** * Save/Update Complete Curriculum Tree Manifest */