mirror of
https://github.com/nihonbuzz/nihonbuzz-academy.git
synced 2026-01-26 05:25:37 +07:00
167 lines
6.1 KiB
PHP
167 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Course;
|
|
use App\Models\Lesson;
|
|
use App\Models\Enrollment;
|
|
use App\Models\UserProgress;
|
|
use Illuminate\Http\Request;
|
|
use Inertia\Inertia;
|
|
use Inertia\Response;
|
|
|
|
class CoursePlayerController extends Controller
|
|
{
|
|
/**
|
|
* Display the course player.
|
|
*/
|
|
public function show(Request $request, string $courseSlug, string $lessonSlug = null): Response
|
|
{
|
|
$user = $request->user();
|
|
|
|
$course = Course::where('slug', $courseSlug)
|
|
->with(['modules.lessons'])
|
|
->firstOrFail();
|
|
|
|
// Check enrollment
|
|
$isEnrolled = Enrollment::where('user_id', $user->id)
|
|
->where('course_id', $course->id)
|
|
->exists();
|
|
|
|
if (!$isEnrolled) {
|
|
return Inertia::render('Errors/Unauthorized', [
|
|
'message' => 'Anda belum terdaftar di kursus ini.'
|
|
]);
|
|
}
|
|
|
|
// Get all lessons for navigation and progress check
|
|
$allLessons = $course->modules->flatMap->lessons;
|
|
|
|
// Find current lesson with vocabularies
|
|
$currentLesson = $lessonSlug
|
|
? Lesson::where('slug', $lessonSlug)->with('vocabularies')->firstOrFail()
|
|
: $allLessons->first()->load('vocabularies');
|
|
|
|
// Navigation Logic (Next/Prev)
|
|
$currentIndex = $allLessons->search(function ($item) use ($currentLesson) {
|
|
return $item->id === $currentLesson->id;
|
|
});
|
|
|
|
$prevLesson = $currentIndex > 0 ? $allLessons[$currentIndex - 1] : null;
|
|
$nextLesson = $currentIndex < $allLessons->count() - 1 ? $allLessons[$currentIndex + 1] : null;
|
|
|
|
// Get user progress for this course
|
|
$completedLessonsIds = UserProgress::where('user_id', $user->id)
|
|
->whereIn('lesson_id', $allLessons->pluck('id'))
|
|
->whereNotNull('completed_at')
|
|
->pluck('lesson_id')
|
|
->toArray();
|
|
|
|
// Initialize started_at if first time viewing
|
|
UserProgress::firstOrCreate(
|
|
['user_id' => $user->id, 'lesson_id' => $currentLesson->id],
|
|
['started_at' => now(), 'last_heartbeat_at' => now()]
|
|
);
|
|
|
|
return Inertia::render('Courses/Learn', [
|
|
'course' => [
|
|
'id' => $course->id,
|
|
'title' => $course->title,
|
|
'slug' => $course->slug,
|
|
'progress_percentage' => count($allLessons) > 0 ? round((count($completedLessonsIds) / count($allLessons)) * 100) : 0,
|
|
],
|
|
'modules' => $course->modules->map(function ($module) use ($completedLessonsIds) {
|
|
return [
|
|
'id' => $module->id,
|
|
'title' => $module->title,
|
|
'lessons' => $module->lessons->map(function ($lesson) use ($completedLessonsIds) {
|
|
return [
|
|
'id' => $lesson->id,
|
|
'title' => $lesson->title,
|
|
'slug' => $lesson->slug,
|
|
'type' => $lesson->type,
|
|
'duration_seconds' => $lesson->duration_seconds,
|
|
'is_completed' => in_array($lesson->id, $completedLessonsIds),
|
|
];
|
|
})
|
|
];
|
|
}),
|
|
'lesson' => [
|
|
'id' => $currentLesson->id,
|
|
'title' => $currentLesson->title,
|
|
'slug' => $currentLesson->slug,
|
|
'type' => $currentLesson->type,
|
|
'content' => $currentLesson->content,
|
|
'video_url' => $currentLesson->video_url,
|
|
// Prefer Spatie Media Library URL (R2), fallback to legacy column
|
|
'content_pdf' => $currentLesson->getFirstMediaUrl('content_pdf') ?: $currentLesson->content_pdf,
|
|
'duration_seconds' => $currentLesson->duration_seconds,
|
|
'is_completed' => in_array($currentLesson->id, $completedLessonsIds),
|
|
'vocabularies' => $currentLesson->vocabularies,
|
|
'next_lesson_slug' => $nextLesson?->slug,
|
|
'prev_lesson_slug' => $prevLesson?->slug,
|
|
'attachments' => $currentLesson->getMedia('attachments')->map(function ($media) {
|
|
return [
|
|
'id' => $media->uuid,
|
|
'name' => $media->file_name,
|
|
'url' => $media->getUrl(),
|
|
'size' => $media->human_readable_size,
|
|
];
|
|
}),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Mark a lesson as completed.
|
|
*/
|
|
public function complete(Request $request, Lesson $lesson)
|
|
{
|
|
$user = $request->user();
|
|
|
|
$alreadyCompleted = UserProgress::where('user_id', $user->id)
|
|
->where('lesson_id', $lesson->id)
|
|
->exists();
|
|
|
|
UserProgress::updateOrCreate(
|
|
['user_id' => $user->id, 'lesson_id' => $lesson->id],
|
|
['completed_at' => now()]
|
|
);
|
|
|
|
if (!$alreadyCompleted) {
|
|
$user->increment('xp_points', 50);
|
|
}
|
|
|
|
return back()->with('success', 'Materi selesai! +50 XP');
|
|
}
|
|
|
|
/**
|
|
* Update learning duration via heartbeat.
|
|
*/
|
|
public function heartbeat(Request $request, Lesson $lesson)
|
|
{
|
|
$user = $request->user();
|
|
$now = now();
|
|
|
|
$progress = UserProgress::firstOrCreate(
|
|
['user_id' => $user->id, 'lesson_id' => $lesson->id],
|
|
['started_at' => $now, 'last_heartbeat_at' => $now]
|
|
);
|
|
|
|
$lastHeartbeat = $progress->last_heartbeat_at ?? $progress->created_at;
|
|
$diffSeconds = $now->diffInSeconds($lastHeartbeat);
|
|
|
|
// Limit diff to prevent massive jumps (e.g. if user leaves tab open and comes back hours later)
|
|
// Max 90 seconds per heartbeat (assuming heartbeat is every 60s)
|
|
$increment = min($diffSeconds, 90);
|
|
|
|
$progress->increment('time_spent_seconds', $increment);
|
|
$progress->update(['last_heartbeat_at' => $now]);
|
|
|
|
return response()->json([
|
|
'status' => 'success',
|
|
'time_spent' => $progress->time_spent_seconds,
|
|
]);
|
|
}
|
|
}
|