Files
SkinbaseNova/app/Services/Academy/AcademyProgressService.php

226 lines
8.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Academy;
use App\Models\AcademyCourse;
use App\Models\AcademyLesson;
use App\Models\AcademyLessonProgress;
use App\Models\AcademyPromptTemplate;
use App\Models\AcademySavedPrompt;
use App\Models\AcademySave;
use App\Models\AcademyUserProgress;
use App\Models\User;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use App\Support\AcademyAnalytics\AcademyAnalyticsEventType;
use App\Support\AcademyAnalytics\AcademyAnalyticsProgressStatus;
use Illuminate\Http\Request;
final class AcademyProgressService
{
public function __construct(
private readonly AcademyBadgeService $badges,
private readonly AcademyCourseProgressService $courses,
private readonly AcademyAnalyticsService $analytics,
) {
}
public function startLesson(User $user, int $lessonId, ?int $courseId = null, ?Request $request = null): AcademyUserProgress
{
$progress = $this->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<string, mixed> $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),
],
]);
}
}