validate([ 'q' => ['nullable', 'string', 'max:120'], 'category' => ['nullable', 'string', 'max:140'], 'difficulty' => ['nullable', 'string', 'max:40'], ]); $query = AcademyLesson::query() ->with('category') ->active() ->published() ->latest('published_at'); if (filled($filters['q'] ?? null)) { $query->where(function ($builder) use ($filters): void { $builder->where('title', 'like', '%'.$filters['q'].'%') ->orWhere('excerpt', 'like', '%'.$filters['q'].'%'); }); } if (filled($filters['category'] ?? null)) { $query->whereHas('category', fn ($builder) => $builder->where('slug', $filters['category'])); } if (filled($filters['difficulty'] ?? null)) { $query->where('difficulty', $filters['difficulty']); } $lessons = $query->paginate(12)->withQueryString(); $lessons->getCollection()->transform(fn (AcademyLesson $lesson): array => $this->access->lessonPayload($lesson, $request->user())); $seo = app(SeoFactory::class) ->collectionListing( 'Academy Lessons — Skinbase', 'Browse Skinbase AI Academy lessons covering prompting, workflow cleanup, ethics, and upload-ready creative workflows.', route('academy.lessons.index', $request->query()), ) ->toArray(); return Inertia::render('Academy/List', [ 'pageType' => 'lessons', 'title' => 'Academy lessons', 'description' => 'Step-by-step tutorials and workflow guides for AI-assisted creative work on Skinbase.', 'seo' => $seo, 'items' => $lessons, 'filters' => $filters, 'categories' => $this->cache->categoriesByType('lesson'), 'pricingUrl' => route('academy.pricing'), ])->rootView('collections'); } public function show(Request $request, string $slug): Response { abort_unless((bool) config('academy.enabled', true), 404); $lesson = AcademyLesson::query() ->with(['category', 'activeBlocks.activeComparisonResults']) ->active() ->published() ->where('slug', $slug) ->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() : []; $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( (string) ($lesson->seo_title ?? ($lesson->title.' — Skinbase Academy')), $description, $canonical, $lesson->cover_image, )->toArray(); return Inertia::render('Academy/Show', [ 'pageType' => 'lesson', 'item' => $payload, 'relatedLessons' => $relatedLessons, '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, ])->rootView('collections'); } }