Update Saqel Platform: 2026-09-02 08:12:57
This commit is contained in:
@@ -103,6 +103,11 @@ class CurriculumController
|
||||
}
|
||||
|
||||
$processingDir = __DIR__ . '/../../storage/curriculum/processing';
|
||||
$taskId = basename($taskId);
|
||||
if (!preg_match('/^task_[A-Za-z0-9_.-]+$|^video_[A-Za-z0-9_.-]+$/', $taskId)) {
|
||||
$response->status(400)->json(['status' => 'error', 'message' => 'رقم المهمة غير صالح']);
|
||||
return;
|
||||
}
|
||||
$stateFile = $processingDir . '/' . $taskId . '.json';
|
||||
|
||||
if (!file_exists($stateFile)) {
|
||||
@@ -261,65 +266,30 @@ class CurriculumController
|
||||
}
|
||||
|
||||
try {
|
||||
// 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.
|
||||
$manifestLesson = CurriculumService::findLessonByFile($file);
|
||||
$title = $manifestLesson['title'] ?? basename($file, '.md');
|
||||
$existingLesson = \App\Core\Database::selectOne(
|
||||
"SELECT id, course_id FROM lessons WHERE curriculum_key = ? OR title = ? OR markdown_content LIKE ? LIMIT 1",
|
||||
[$file, $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. Bind the upload to the synchronized Manifest lesson when present.
|
||||
if ($existingLesson) {
|
||||
$lessonId = (int)$existingLesson['id'];
|
||||
\App\Core\Database::query(
|
||||
"UPDATE lessons SET curriculum_key = ?, 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 = ?",
|
||||
[$file, $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, curriculum_key, 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, $file, $uploadResult['video_uuid'], $uploadResult['local_path'], $uploadResult['hls_url'], $uploadResult['thumbnail_url'], $uploadResult['duration'] ?? 0, $videoUrl]
|
||||
);
|
||||
// Video transcoding and R2 upload can take longer than a web request.
|
||||
// Persist the upload and hand the heavy work to a CLI worker.
|
||||
$processingDir = __DIR__ . '/../../storage/curriculum/processing';
|
||||
if (!is_dir($processingDir)) mkdir($processingDir, 0755, true);
|
||||
$taskId = uniqid('video_', true);
|
||||
$extension = strtolower(pathinfo($_FILES['video_file']['name'], PATHINFO_EXTENSION)) ?: 'mp4';
|
||||
$savedVideoPath = $processingDir . '/' . $taskId . '.' . $extension;
|
||||
if (!move_uploaded_file($_FILES['video_file']['tmp_name'], $savedVideoPath)) {
|
||||
throw new \RuntimeException('تعذر حفظ ملف الفيديو المؤقت على السيرفر.');
|
||||
}
|
||||
file_put_contents($processingDir . '/' . $taskId . '.json', json_encode([
|
||||
'task_id' => $taskId, 'status' => 'queued', 'progress' => 5,
|
||||
'message' => 'تم استلام الفيديو، وسيبدأ التحويل والرفع السحابي الآن.',
|
||||
'file' => $file, 'video_path' => $savedVideoPath,
|
||||
'original_name' => $_FILES['video_file']['name']
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 3. Trigger Autonomous Zero-Touch AI Analysis & Socratic Checkpoint Generation
|
||||
\App\Services\AiVideoAnalyzerService::processLessonAutonomously($lessonId);
|
||||
$response->status(202)->json(['status' => 'processing', 'task_id' => $taskId, 'message' => 'تم استلام الفيديو وجاري تحويله ورفعه إلى Cloudflare R2...']);
|
||||
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
|
||||
elseif (ob_get_level()) { ob_end_flush(); flush(); }
|
||||
$scriptPath = realpath(__DIR__ . '/../../scripts/video_upload_worker.php');
|
||||
exec('php ' . escapeshellarg($scriptPath) . ' ' . escapeshellarg($taskId) . ' > /dev/null 2>&1 &');
|
||||
return;
|
||||
|
||||
// 4. Update Lesson AI assets in manifest.json & filesystem
|
||||
$currentAssets = CurriculumService::getLessonAiAssets($file);
|
||||
$currentAssets['ai_video_url'] = $videoUrl;
|
||||
CurriculumService::saveLessonAiAssets($file, $currentAssets);
|
||||
|
||||
// 5. Update any existing curriculum lesson rows with this video
|
||||
try {
|
||||
\App\Core\Database::query(
|
||||
"UPDATE lessons SET ai_video_url = ? WHERE title LIKE ? OR markdown_content LIKE ?",
|
||||
[$videoUrl, "%{$title}%", "%{$file}%"]
|
||||
);
|
||||
} catch (\Throwable $dbEx) {
|
||||
error_log("Video DB sync note: " . $dbEx->getMessage());
|
||||
}
|
||||
|
||||
$response->status(201)->json([
|
||||
'status' => 'success',
|
||||
'message' => 'تم رفع وتثبيت فيديو الشرح ومعالجته HLS وربطه بالذكاء الاصطناعي بنجاح!',
|
||||
'video_url' => $videoUrl,
|
||||
'course_id' => $courseId,
|
||||
'lesson_id' => $lessonId,
|
||||
'data' => $uploadResult
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$response->status(500)->json([
|
||||
'status' => 'error',
|
||||
|
||||
Reference in New Issue
Block a user