updateUserProgressRecord($user, $courseId, $lessonId, [ 'status' => AcademyAnalyticsProgressStatus::STARTED, 'progress_percent' => 0, 'started_at' => now(), 'completed_at' => null, 'last_seen_at' => now(), ]); if ($courseId) { $course = AcademyCourse::query()->find($courseId); if ($course instanceof AcademyCourse) { $this->courses->markEnrollmentStarted($user, $course); $lesson = AcademyLesson::query()->find($lessonId); if ($lesson instanceof AcademyLesson) { $this->courses->updateLastLesson($user, $course, $lesson); } $this->syncCourseProgressRecord($user, $course); } } $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::LESSON_STARTED, 'content_type' => AcademyAnalyticsContentType::LESSON, 'content_id' => $lessonId, 'metadata' => array_filter([ 'course_id' => $courseId, ], static fn (mixed $value): bool => $value !== null), ], $user, $request); return $progress; } public function completeLesson(User $user, int $lessonId, ?int $courseId = null, ?Request $request = null): AcademyUserProgress { $progress = $this->updateUserProgressRecord($user, $courseId, $lessonId, [ 'status' => AcademyAnalyticsProgressStatus::COMPLETED, 'progress_percent' => 100, 'started_at' => now(), 'completed_at' => now(), 'last_seen_at' => now(), ]); if ($courseId) { $course = AcademyCourse::query()->find($courseId); if ($course instanceof AcademyCourse) { $this->syncCourseProgressRecord($user, $course); } } $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::LESSON_COMPLETED, 'content_type' => AcademyAnalyticsContentType::LESSON, 'content_id' => $lessonId, 'metadata' => array_filter([ 'course_id' => $courseId, ], static fn (mixed $value): bool => $value !== null), ], $user, $request); return $progress; } public function startCourse(User $user, int $courseId, ?Request $request = null): AcademyUserProgress { $course = AcademyCourse::query()->findOrFail($courseId); $this->courses->markEnrollmentStarted($user, $course); $progress = $this->syncCourseProgressRecord($user, $course, true); $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::COURSE_STARTED, 'content_type' => AcademyAnalyticsContentType::COURSE, 'content_id' => $courseId, ], $user, $request); return $progress; } public function completeCourse(User $user, int $courseId, ?Request $request = null): AcademyUserProgress { $course = AcademyCourse::query()->findOrFail($courseId); $this->courses->markCourseCompletedIfFinished($user, $course); $progress = $this->syncCourseProgressRecord($user, $course); if ($progress->status === AcademyAnalyticsProgressStatus::COMPLETED) { $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::COURSE_COMPLETED, 'content_type' => AcademyAnalyticsContentType::COURSE, 'content_id' => $courseId, ], $user, $request); } return $progress; } public function markLessonComplete(User $user, AcademyLesson $lesson, ?AcademyCourse $course = null, ?Request $request = null): AcademyLessonProgress { $progress = AcademyLessonProgress::query()->updateOrCreate( [ 'lesson_id' => $lesson->id, 'user_id' => $user->id, ], [ 'completed_at' => now(), ], ); if ($course instanceof AcademyCourse) { $this->courses->updateLastLesson($user, $course, $lesson); $this->courses->markCourseCompletedIfFinished($user, $course); } $this->completeLesson($user, (int) $lesson->id, $course?->id, $request); $this->badges->syncForUser($user); return $progress; } public function savePrompt(User $user, AcademyPromptTemplate $prompt): AcademySavedPrompt { $saved = AcademySavedPrompt::query()->firstOrCreate([ 'prompt_template_id' => $prompt->id, 'user_id' => $user->id, ]); AcademySave::query()->firstOrCreate([ 'user_id' => $user->id, 'content_type' => AcademyAnalyticsContentType::PROMPT, 'content_id' => $prompt->id, ]); $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::PROMPT_SAVE, 'content_type' => AcademyAnalyticsContentType::PROMPT, 'content_id' => (int) $prompt->id, ], $user); $this->badges->syncForUser($user); return $saved; } public function unsavePrompt(User $user, AcademyPromptTemplate $prompt): void { AcademySavedPrompt::query() ->where('prompt_template_id', $prompt->id) ->where('user_id', $user->id) ->delete(); AcademySave::query() ->where('user_id', $user->id) ->where('content_type', AcademyAnalyticsContentType::PROMPT) ->where('content_id', $prompt->id) ->delete(); $this->badges->syncForUser($user); } /** * @param array $attributes */ private function updateUserProgressRecord(User $user, ?int $courseId, ?int $lessonId, array $attributes): AcademyUserProgress { return AcademyUserProgress::query()->updateOrCreate( [ 'user_id' => $user->id, 'course_id' => $courseId, 'lesson_id' => $lessonId, ], $attributes, ); } private function syncCourseProgressRecord(User $user, AcademyCourse $course, bool $forceStarted = false): AcademyUserProgress { $progressPercent = $this->courses->getProgressPercent($user, $course); $isComplete = $this->courses->getTotalRequiredLessonsCount($course) > 0 && $progressPercent >= 100; $status = $isComplete ? AcademyAnalyticsProgressStatus::COMPLETED : ($progressPercent > 0 || $forceStarted ? AcademyAnalyticsProgressStatus::IN_PROGRESS : AcademyAnalyticsProgressStatus::STARTED); return $this->updateUserProgressRecord($user, (int) $course->id, null, [ 'status' => $status, 'progress_percent' => $progressPercent, 'started_at' => now(), 'completed_at' => $isComplete ? now() : null, 'last_seen_at' => now(), 'metadata' => [ 'completed_required' => $this->courses->getCompletedRequiredLessonsCount($user, $course), 'total_required' => $this->courses->getTotalRequiredLessonsCount($course), ], ]); } }