chore: commit remaining workspace changes
This commit is contained in:
184
app/Http/Controllers/Academy/AcademyCourseController.php
Normal file
184
app/Http/Controllers/Academy/AcademyCourseController.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyCourseLesson;
|
||||
use App\Services\Academy\AcademyAccessService;
|
||||
use App\Services\Academy\AcademyCacheService;
|
||||
use App\Services\Academy\AcademyCourseNavigationService;
|
||||
use App\Services\Academy\AcademyCourseProgressService;
|
||||
use App\Support\Seo\SeoFactory;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
final class AcademyCourseController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AcademyAccessService $access,
|
||||
private readonly AcademyCacheService $cache,
|
||||
private readonly AcademyCourseNavigationService $navigation,
|
||||
private readonly AcademyCourseProgressService $progress,
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
abort_unless((bool) config('academy.enabled', true), 404);
|
||||
|
||||
$filters = $request->validate([
|
||||
'difficulty' => ['nullable', 'string', 'max:40'],
|
||||
'access' => ['nullable', 'string', 'max:40'],
|
||||
]);
|
||||
|
||||
$query = AcademyCourse::query()->published()->ordered();
|
||||
|
||||
if (filled($filters['difficulty'] ?? null)) {
|
||||
$query->where('difficulty', $filters['difficulty']);
|
||||
}
|
||||
|
||||
if (filled($filters['access'] ?? null)) {
|
||||
$query->where('access_level', $filters['access']);
|
||||
}
|
||||
|
||||
$courses = $query->paginate(12)->withQueryString();
|
||||
$courses->getCollection()->transform(function (AcademyCourse $course) use ($request): array {
|
||||
return $this->access->coursePayload($course, $request->user(), [
|
||||
'progress' => $this->progress->getProgress($request->user(), $course),
|
||||
]);
|
||||
});
|
||||
|
||||
$featuredCourses = collect($this->cache->featuredCourses())->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user(), [
|
||||
'progress' => $this->progress->getProgress($request->user(), $course),
|
||||
]))->values();
|
||||
|
||||
$seoCourses = $featuredCourses
|
||||
->concat(collect($courses->items()))
|
||||
->unique(fn (array $course): string => (string) ($course['slug'] ?? ''))
|
||||
->values();
|
||||
|
||||
$seo = app(SeoFactory::class)
|
||||
->academyCourseListingPage(
|
||||
'Academy Courses — Skinbase',
|
||||
'Follow guided Skinbase AI Academy courses built from reusable lessons, chapters, and creator workflows.',
|
||||
route('academy.courses.index', $request->query()),
|
||||
$seoCourses,
|
||||
[
|
||||
['name' => 'Academy', 'url' => route('academy.index')],
|
||||
['name' => 'Courses', 'url' => route('academy.courses.index')],
|
||||
],
|
||||
)
|
||||
->toArray();
|
||||
|
||||
return Inertia::render('Academy/CoursesIndex', [
|
||||
'seo' => $seo,
|
||||
'title' => 'Academy courses',
|
||||
'description' => 'Guided learning paths built from reusable Academy lessons and creator workflows.',
|
||||
'items' => $courses,
|
||||
'featuredCourses' => $featuredCourses->all(),
|
||||
'filters' => $filters,
|
||||
'pricingUrl' => route('academy.pricing'),
|
||||
])->rootView('collections');
|
||||
}
|
||||
|
||||
public function show(Request $request, AcademyCourse $course): Response
|
||||
{
|
||||
abort_unless((bool) config('academy.enabled', true), 404);
|
||||
abort_unless($course->isPublished(), 404);
|
||||
|
||||
$course->load(['sections', 'courseLessons.section', 'courseLessons.lesson.category']);
|
||||
|
||||
$progress = $this->progress->getProgress($request->user(), $course);
|
||||
$completedLessonIds = $request->user() ? $this->progress->getCompletedLessonIds($request->user(), $course) : [];
|
||||
$orderedLessons = $this->navigation->orderedCourseLessons($course);
|
||||
$stepMeta = $orderedLessons
|
||||
->values()
|
||||
->mapWithKeys(fn (AcademyCourseLesson $courseLesson, int $index): array => [
|
||||
$courseLesson->id => [
|
||||
'course_step_number' => $index + 1,
|
||||
'course_step_label' => sprintf('Step %02d', $index + 1),
|
||||
],
|
||||
]);
|
||||
$sections = $course->sections
|
||||
->sortBy([['order_num', 'asc'], ['id', 'asc']])
|
||||
->values()
|
||||
->map(function ($section) use ($completedLessonIds, $orderedLessons, $request, $stepMeta): array {
|
||||
$sectionLessons = $orderedLessons
|
||||
->where('section_id', $section->id)
|
||||
->values()
|
||||
->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [
|
||||
'completed_lesson_ids' => $completedLessonIds,
|
||||
...((array) $stepMeta->get($courseLesson->id, [])),
|
||||
]))
|
||||
->all();
|
||||
|
||||
return [
|
||||
'id' => (int) $section->id,
|
||||
'title' => (string) $section->title,
|
||||
'slug' => (string) ($section->slug ?? ''),
|
||||
'description' => (string) ($section->description ?? ''),
|
||||
'order_num' => (int) ($section->order_num ?? 0),
|
||||
'is_visible' => (bool) ($section->is_visible ?? true),
|
||||
'lessons' => $sectionLessons,
|
||||
];
|
||||
})
|
||||
->all();
|
||||
|
||||
$unsectionedLessons = $orderedLessons
|
||||
->whereNull('section_id')
|
||||
->values()
|
||||
->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [
|
||||
'completed_lesson_ids' => $completedLessonIds,
|
||||
...((array) $stepMeta->get($courseLesson->id, [])),
|
||||
]))
|
||||
->all();
|
||||
|
||||
$coursePayload = $this->access->coursePayload($course, $request->user(), ['progress' => $progress]);
|
||||
$courseKeywords = collect(explode(',', (string) ($course->meta_keywords ?? '')))
|
||||
->map(fn (string $keyword): string => trim($keyword))
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
$courseImage = (string) ($coursePayload['cover_image_url'] ?? $coursePayload['teaser_image_url'] ?? $course->og_image ?? $course->cover_image ?? $course->teaser_image ?? '');
|
||||
|
||||
$seo = app(SeoFactory::class)
|
||||
->academyCoursePage(
|
||||
(string) ($course->seo_title ?: ($course->title . ' — Skinbase Academy')),
|
||||
(string) ($course->seo_description ?: $course->excerpt ?: 'Skinbase Academy course'),
|
||||
route('academy.courses.show', ['course' => $course->slug]),
|
||||
$courseImage,
|
||||
[
|
||||
['name' => 'Academy', 'url' => route('academy.index')],
|
||||
['name' => 'Courses', 'url' => route('academy.courses.index')],
|
||||
['name' => (string) $course->title, 'url' => route('academy.courses.show', ['course' => $course->slug])],
|
||||
],
|
||||
$courseKeywords,
|
||||
$course->published_at?->toAtomString(),
|
||||
$course->updated_at?->toAtomString(),
|
||||
(string) ($course->access_level ?? ''),
|
||||
(string) ($course->difficulty ?? ''),
|
||||
(int) ($course->estimated_minutes ?? 0),
|
||||
$orderedLessons
|
||||
->values()
|
||||
->map(fn (AcademyCourseLesson $courseLesson): array => $this->access->courseLessonPayload($courseLesson, $request->user(), false, [
|
||||
'completed_lesson_ids' => $completedLessonIds,
|
||||
...((array) $stepMeta->get($courseLesson->id, [])),
|
||||
]))
|
||||
->all(),
|
||||
)
|
||||
->toArray();
|
||||
|
||||
return Inertia::render('Academy/CoursesShow', [
|
||||
'seo' => $seo,
|
||||
'course' => $coursePayload,
|
||||
'sections' => $sections,
|
||||
'unsectionedLessons' => $unsectionedLessons,
|
||||
'pricingUrl' => route('academy.pricing'),
|
||||
'startUrl' => $request->user() ? route('academy.courses.start', ['course' => $course->slug]) : null,
|
||||
])->rootView('collections');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Services\Academy\AcademyCourseProgressService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
final class AcademyCourseEnrollmentController extends Controller
|
||||
{
|
||||
public function __construct(private readonly AcademyCourseProgressService $progress)
|
||||
{
|
||||
}
|
||||
|
||||
public function start(Request $request, AcademyCourse $course): RedirectResponse
|
||||
{
|
||||
abort_unless((bool) config('academy.enabled', true), 404);
|
||||
abort_unless($course->isPublished(), 404);
|
||||
|
||||
$this->progress->markEnrollmentStarted($request->user(), $course);
|
||||
$continueLesson = $this->progress->getContinueLesson($request->user(), $course);
|
||||
|
||||
if ($continueLesson?->lesson) {
|
||||
return redirect()->route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $continueLesson->lesson->slug]);
|
||||
}
|
||||
|
||||
return redirect()->route('academy.courses.show', ['course' => $course->slug]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Services\Academy\AcademyAccessService;
|
||||
use App\Services\Academy\AcademyCourseNavigationService;
|
||||
use App\Services\Academy\AcademyCourseProgressService;
|
||||
use App\Support\Seo\SeoFactory;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
final class AcademyCourseLessonController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AcademyAccessService $access,
|
||||
private readonly AcademyCourseNavigationService $navigation,
|
||||
private readonly AcademyCourseProgressService $progress,
|
||||
) {
|
||||
}
|
||||
|
||||
public function show(Request $request, AcademyCourse $course, AcademyLesson $lesson): Response
|
||||
{
|
||||
abort_unless((bool) config('academy.enabled', true), 404);
|
||||
abort_unless($course->isPublished(), 404);
|
||||
|
||||
$course->load(['sections', 'courseLessons.section', 'courseLessons.lesson.category']);
|
||||
$courseLesson = $this->navigation->findCourseLesson($course, $lesson);
|
||||
|
||||
abort_unless($courseLesson instanceof \App\Models\AcademyCourseLesson, 404);
|
||||
|
||||
if ($request->user()) {
|
||||
$this->progress->updateLastLesson($request->user(), $course, $lesson);
|
||||
$this->progress->markCourseCompletedIfFinished($request->user(), $course);
|
||||
}
|
||||
|
||||
$progress = $this->progress->getProgress($request->user(), $course);
|
||||
$previousLesson = $this->navigation->previousLesson($course, $lesson);
|
||||
$nextLesson = $this->navigation->nextLesson($course, $lesson);
|
||||
$courseOutline = $this->navigation->orderedCourseLessons($course)
|
||||
->map(fn (\App\Models\AcademyCourseLesson $entry): array => $this->access->courseLessonPayload($entry, $request->user()))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$payload = $this->access->courseLessonPayload($courseLesson, $request->user(), true);
|
||||
$canonical = route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]);
|
||||
$description = Str::limit(trim((string) ($lesson->seo_description ?? $lesson->excerpt ?? 'Skinbase Academy course lesson.')), 160, '...');
|
||||
$seo = app(SeoFactory::class)->academyLessonPage(
|
||||
(string) ($lesson->seo_title ?? ($lesson->title . ' — ' . $course->title)),
|
||||
$description,
|
||||
$canonical,
|
||||
(string) ($payload['article_cover_image_url'] ?? $payload['cover_image_url'] ?? $lesson->cover_image ?? ''),
|
||||
[
|
||||
['name' => 'Academy', 'url' => route('academy.index')],
|
||||
['name' => 'Courses', 'url' => route('academy.courses.index')],
|
||||
['name' => (string) $course->title, 'url' => route('academy.courses.show', ['course' => $course->slug])],
|
||||
['name' => (string) $lesson->title, 'url' => $canonical],
|
||||
],
|
||||
array_values((array) ($payload['tags'] ?? [])),
|
||||
$lesson->published_at?->toAtomString(),
|
||||
$lesson->updated_at?->toAtomString(),
|
||||
(string) $course->title,
|
||||
)->toArray();
|
||||
|
||||
return Inertia::render('Academy/Show', [
|
||||
'pageType' => 'lesson',
|
||||
'item' => $payload,
|
||||
'relatedLessons' => [],
|
||||
'relatedCourses' => [],
|
||||
'previousLesson' => $previousLesson ? $this->access->courseLessonPayload($previousLesson, $request->user()) : null,
|
||||
'nextLesson' => $nextLesson ? $this->access->courseLessonPayload($nextLesson, $request->user()) : null,
|
||||
'seo' => $seo,
|
||||
'pricingUrl' => route('academy.pricing'),
|
||||
'completeUrl' => $request->user() ? route('academy.lessons.complete', ['lesson' => $lesson->id]) : null,
|
||||
'completed' => $request->user()?->academyLessonProgress()->where('lesson_id', $lesson->id)->whereNotNull('completed_at')->exists() ?? false,
|
||||
'courseContext' => [
|
||||
'id' => (int) $course->id,
|
||||
'title' => (string) $course->title,
|
||||
'slug' => (string) $course->slug,
|
||||
'subtitle' => (string) ($course->subtitle ?? ''),
|
||||
'showUrl' => route('academy.courses.show', ['course' => $course->slug]),
|
||||
'completePayload' => ['course_id' => $course->id],
|
||||
'progress' => [
|
||||
'percent' => (int) ($progress['progress_percent'] ?? 0),
|
||||
'completedRequired' => (int) ($progress['completed_required'] ?? 0),
|
||||
'totalRequired' => (int) ($progress['total_required'] ?? 0),
|
||||
'completed' => (bool) ($progress['completed'] ?? false),
|
||||
],
|
||||
'outline' => $courseOutline,
|
||||
],
|
||||
])->rootView('collections');
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyChallenge;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Models\AcademyPromptTemplate;
|
||||
use App\Services\Academy\AcademyAccessService;
|
||||
@@ -41,11 +42,13 @@ final class AcademyHomeController extends Controller
|
||||
$home = $this->cache->homePayload(function (): array {
|
||||
return [
|
||||
'featuredLessons' => $this->cache->featuredLessons(),
|
||||
'featuredCourses' => $this->cache->featuredCourses(),
|
||||
'featuredPrompts' => $this->cache->featuredPrompts(),
|
||||
'featuredChallenges' => (bool) config('academy.challenges_enabled', true)
|
||||
? $this->cache->featuredChallenges()
|
||||
: [],
|
||||
'lessonCount' => AcademyLesson::query()->active()->published()->count(),
|
||||
'courseCount' => AcademyCourse::query()->published()->count(),
|
||||
'promptCount' => AcademyPromptTemplate::query()->active()->published()->count(),
|
||||
'challengeCount' => (bool) config('academy.challenges_enabled', true)
|
||||
? AcademyChallenge::query()->publiclyVisible()->count()
|
||||
@@ -58,6 +61,7 @@ final class AcademyHomeController extends Controller
|
||||
'pricingUrl' => route('academy.pricing'),
|
||||
'links' => [
|
||||
'lessons' => route('academy.lessons.index'),
|
||||
'courses' => route('academy.courses.index'),
|
||||
'prompts' => route('academy.prompts.index'),
|
||||
'packs' => route('academy.packs.index'),
|
||||
'challenges' => route('academy.challenges.index'),
|
||||
@@ -69,9 +73,11 @@ final class AcademyHomeController extends Controller
|
||||
],
|
||||
'stats' => [
|
||||
'lessonCount' => (int) $home['lessonCount'],
|
||||
'courseCount' => (int) $home['courseCount'],
|
||||
'promptCount' => (int) $home['promptCount'],
|
||||
'challengeCount' => (int) $home['challengeCount'],
|
||||
],
|
||||
'featuredCourses' => collect($home['featuredCourses'])->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user()))->values()->all(),
|
||||
'featuredLessons' => collect($home['featuredLessons'])->map(fn (AcademyLesson $lesson): array => $this->access->lessonPayload($lesson, $request->user()))->values()->all(),
|
||||
'featuredPrompts' => collect($home['featuredPrompts'])->map(fn (AcademyPromptTemplate $prompt): array => $this->access->promptPayload($prompt, $request->user()))->values()->all(),
|
||||
'featuredChallenges' => collect($home['featuredChallenges'])->map(fn (AcademyChallenge $challenge): array => $this->access->challengePayload($challenge, $request->user(), true))->values()->all(),
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Services\Academy\AcademyAccessService;
|
||||
use App\Services\Academy\AcademyCacheService;
|
||||
@@ -35,7 +36,7 @@ final class AcademyLessonController extends Controller
|
||||
->with('category')
|
||||
->active()
|
||||
->published()
|
||||
->latest('published_at');
|
||||
->orderedForCourse();
|
||||
|
||||
if (filled($filters['q'] ?? null)) {
|
||||
$query->where(function ($builder) use ($filters): void {
|
||||
@@ -87,33 +88,73 @@ final class AcademyLessonController extends Controller
|
||||
->firstOrFail();
|
||||
|
||||
$payload = $this->access->lessonPayload($lesson, $request->user(), true);
|
||||
$relatedLessons = $lesson->category_id !== null
|
||||
? AcademyLesson::query()
|
||||
->with('category')
|
||||
->active()
|
||||
->published()
|
||||
->where('category_id', $lesson->category_id)
|
||||
->where('id', '!=', $lesson->id)
|
||||
->orderByDesc('published_at')
|
||||
->limit(6)
|
||||
->get()
|
||||
->map(fn (AcademyLesson $relatedLesson): array => $this->access->lessonPayload($relatedLesson, $request->user()))
|
||||
->values()
|
||||
->all()
|
||||
: [];
|
||||
$courseQuery = AcademyLesson::query()
|
||||
->with('category')
|
||||
->active()
|
||||
->published();
|
||||
|
||||
if (filled($lesson->series_name)) {
|
||||
$courseQuery->where('series_name', $lesson->series_name);
|
||||
} elseif ($lesson->category_id !== null) {
|
||||
$courseQuery->where('category_id', $lesson->category_id);
|
||||
} else {
|
||||
$courseQuery->whereKey($lesson->id);
|
||||
}
|
||||
|
||||
$courseLessons = $courseQuery
|
||||
->orderedForCourse()
|
||||
->get()
|
||||
->filter(fn (AcademyLesson $courseLesson): bool => $this->access->canAccessLesson($request->user(), $courseLesson))
|
||||
->values();
|
||||
|
||||
$currentIndex = $courseLessons->search(fn (AcademyLesson $courseLesson): bool => $courseLesson->is($lesson));
|
||||
$previousLesson = is_int($currentIndex) && $currentIndex > 0
|
||||
? $courseLessons->get($currentIndex - 1)
|
||||
: null;
|
||||
$nextLesson = is_int($currentIndex) && $currentIndex < ($courseLessons->count() - 1)
|
||||
? $courseLessons->get($currentIndex + 1)
|
||||
: null;
|
||||
|
||||
$relatedLessons = $courseLessons
|
||||
->reject(fn (AcademyLesson $courseLesson): bool => $courseLesson->is($lesson))
|
||||
->take(6)
|
||||
->map(fn (AcademyLesson $relatedLesson): array => $this->access->lessonPayload($relatedLesson, $request->user()))
|
||||
->values()
|
||||
->all();
|
||||
$relatedCourses = AcademyCourse::query()
|
||||
->published()
|
||||
->ordered()
|
||||
->whereHas('courseLessons', fn ($builder) => $builder->where('lesson_id', $lesson->id))
|
||||
->limit(3)
|
||||
->get()
|
||||
->map(fn (AcademyCourse $course): array => $this->access->coursePayload($course, $request->user()))
|
||||
->values()
|
||||
->all();
|
||||
$canonical = route('academy.lessons.show', ['slug' => $lesson->slug]);
|
||||
$description = Str::limit(trim((string) ($lesson->seo_description ?? $lesson->excerpt ?? 'Skinbase Academy lesson.')), 160, '...');
|
||||
$seo = app(SeoFactory::class)->collectionPage(
|
||||
$seo = app(SeoFactory::class)->academyLessonPage(
|
||||
(string) ($lesson->seo_title ?? ($lesson->title.' — Skinbase Academy')),
|
||||
$description,
|
||||
$canonical,
|
||||
$lesson->cover_image,
|
||||
(string) ($payload['article_cover_image_url'] ?? $payload['cover_image_url'] ?? $lesson->cover_image ?? ''),
|
||||
[
|
||||
['name' => 'Academy', 'url' => route('academy.index')],
|
||||
['name' => 'Lessons', 'url' => route('academy.lessons.index')],
|
||||
['name' => (string) $lesson->title, 'url' => $canonical],
|
||||
],
|
||||
array_values((array) ($payload['tags'] ?? [])),
|
||||
$lesson->published_at?->toAtomString(),
|
||||
$lesson->updated_at?->toAtomString(),
|
||||
(string) ($lesson->series_name ?: $lesson->category?->name ?: 'Academy'),
|
||||
)->toArray();
|
||||
|
||||
return Inertia::render('Academy/Show', [
|
||||
'pageType' => 'lesson',
|
||||
'item' => $payload,
|
||||
'relatedLessons' => $relatedLessons,
|
||||
'relatedCourses' => $relatedCourses,
|
||||
'previousLesson' => $previousLesson ? $this->access->lessonPayload($previousLesson, $request->user()) : null,
|
||||
'nextLesson' => $nextLesson ? $this->access->lessonPayload($nextLesson, $request->user()) : null,
|
||||
'seo' => $seo,
|
||||
'pricingUrl' => route('academy.pricing'),
|
||||
'completeUrl' => $request->user() ? route('academy.lessons.complete', ['lesson' => $lesson->id]) : null,
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers\Academy;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AcademyCourse;
|
||||
use App\Models\AcademyLesson;
|
||||
use App\Services\Academy\AcademyAccessService;
|
||||
use App\Services\Academy\AcademyProgressService;
|
||||
@@ -24,7 +25,13 @@ final class AcademyProgressController extends Controller
|
||||
abort_unless((bool) config('academy.enabled', true), 404);
|
||||
abort_unless($this->access->canAccessLesson($request->user(), $lesson), 403);
|
||||
|
||||
$record = $this->progress->markLessonComplete($request->user(), $lesson);
|
||||
$course = null;
|
||||
|
||||
if ($request->filled('course_id')) {
|
||||
$course = AcademyCourse::query()->published()->find($request->integer('course_id'));
|
||||
}
|
||||
|
||||
$record = $this->progress->markLessonComplete($request->user(), $lesson, $course);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
|
||||
Reference in New Issue
Block a user