[ 'lessons' => AcademyLesson::query()->count(), 'prompts' => AcademyPromptTemplate::query()->count(), 'packs' => AcademyPromptPack::query()->count(), 'challenges' => AcademyChallenge::query()->count(), 'submissions' => AcademyChallengeSubmission::query()->count(), 'badges' => AcademyBadge::query()->count(), 'creator_subscribers' => 0, 'pro_subscribers' => 0, 'mrr' => 0, ], 'links' => [ 'categories' => route('admin.academy.categories.index'), 'lessons' => route('admin.academy.lessons.index'), 'prompts' => route('admin.academy.prompts.index'), 'packs' => route('admin.academy.packs.index'), 'challenges' => route('admin.academy.challenges.index'), 'submissions' => route('admin.academy.submissions.index'), 'badges' => route('admin.academy.badges.index'), ], ]); } public function categoriesIndex(): Response { return $this->renderIndex('categories'); } public function categoriesCreate(): Response { return $this->renderForm('categories', new AcademyCategory()); } public function categoriesStore(UpsertAcademyCategoryRequest $request): RedirectResponse { $category = new AcademyCategory(); $category->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.categories.edit', ['academyCategory' => $category])->with('success', 'Academy category created.'); } public function categoriesEdit(AcademyCategory $academyCategory): Response { return $this->renderForm('categories', $academyCategory); } public function categoriesUpdate(UpsertAcademyCategoryRequest $request, AcademyCategory $academyCategory): RedirectResponse { $academyCategory->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.categories.edit', ['academyCategory' => $academyCategory])->with('success', 'Academy category updated.'); } public function categoriesDestroy(AcademyCategory $academyCategory): RedirectResponse { $academyCategory->delete(); $this->cache->clearAll(); return redirect()->route('admin.academy.categories.index')->with('success', 'Academy category deleted.'); } public function lessonsIndex(): Response { return $this->renderIndex('lessons'); } public function lessonsCreate(): Response { return $this->renderForm('lessons', new AcademyLesson()); } public function lessonsStore(UpsertAcademyLessonRequest $request): RedirectResponse { $lesson = new AcademyLesson(); $lesson->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.lessons.edit', ['academyLesson' => $lesson])->with('success', 'Academy lesson created.'); } public function lessonsEdit(AcademyLesson $academyLesson): Response { return $this->renderForm('lessons', $academyLesson); } public function lessonsUpdate(UpsertAcademyLessonRequest $request, AcademyLesson $academyLesson): RedirectResponse { $academyLesson->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.lessons.edit', ['academyLesson' => $academyLesson])->with('success', 'Academy lesson updated.'); } public function lessonsDestroy(AcademyLesson $academyLesson): RedirectResponse { $academyLesson->delete(); $this->cache->clearAll(); return redirect()->route('admin.academy.lessons.index')->with('success', 'Academy lesson deleted.'); } public function promptsIndex(): Response { return $this->renderIndex('prompts'); } public function promptsCreate(): Response { return $this->renderForm('prompts', new AcademyPromptTemplate()); } public function promptsStore(UpsertAcademyPromptTemplateRequest $request): RedirectResponse { $prompt = new AcademyPromptTemplate(); $prompt->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.prompts.edit', ['academyPromptTemplate' => $prompt])->with('success', 'Academy prompt created.'); } public function promptsEdit(AcademyPromptTemplate $academyPromptTemplate): Response { return $this->renderForm('prompts', $academyPromptTemplate); } public function promptsUpdate(UpsertAcademyPromptTemplateRequest $request, AcademyPromptTemplate $academyPromptTemplate): RedirectResponse { $academyPromptTemplate->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.prompts.edit', ['academyPromptTemplate' => $academyPromptTemplate])->with('success', 'Academy prompt updated.'); } public function promptsDestroy(AcademyPromptTemplate $academyPromptTemplate): RedirectResponse { $academyPromptTemplate->delete(); $this->cache->clearAll(); return redirect()->route('admin.academy.prompts.index')->with('success', 'Academy prompt deleted.'); } public function packsIndex(): Response { return $this->renderIndex('packs'); } public function packsCreate(): Response { return $this->renderForm('packs', new AcademyPromptPack()); } public function packsStore(UpsertAcademyPromptPackRequest $request): RedirectResponse { $pack = new AcademyPromptPack(); $pack->fill(collect($request->validated())->except('prompt_ids')->all())->save(); $this->syncPackItems($pack, $request->validated('prompt_ids', [])); $this->cache->clearAll(); return redirect()->route('admin.academy.packs.edit', ['academyPromptPack' => $pack])->with('success', 'Academy prompt pack created.'); } public function packsEdit(AcademyPromptPack $academyPromptPack): Response { $academyPromptPack->load('prompts'); return $this->renderForm('packs', $academyPromptPack); } public function packsUpdate(UpsertAcademyPromptPackRequest $request, AcademyPromptPack $academyPromptPack): RedirectResponse { $academyPromptPack->fill(collect($request->validated())->except('prompt_ids')->all())->save(); $this->syncPackItems($academyPromptPack, $request->validated('prompt_ids', [])); $this->cache->clearAll(); return redirect()->route('admin.academy.packs.edit', ['academyPromptPack' => $academyPromptPack])->with('success', 'Academy prompt pack updated.'); } public function packsDestroy(AcademyPromptPack $academyPromptPack): RedirectResponse { $academyPromptPack->delete(); $this->cache->clearAll(); return redirect()->route('admin.academy.packs.index')->with('success', 'Academy prompt pack deleted.'); } public function challengesIndex(): Response { return $this->renderIndex('challenges'); } public function challengesCreate(): Response { return $this->renderForm('challenges', new AcademyChallenge()); } public function challengesStore(UpsertAcademyChallengeRequest $request): RedirectResponse { $challenge = new AcademyChallenge(); $challenge->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.challenges.edit', ['academyChallenge' => $challenge])->with('success', 'Academy challenge created.'); } public function challengesEdit(AcademyChallenge $academyChallenge): Response { return $this->renderForm('challenges', $academyChallenge); } public function challengesUpdate(UpsertAcademyChallengeRequest $request, AcademyChallenge $academyChallenge): RedirectResponse { $academyChallenge->fill($request->validated())->save(); $this->cache->clearAll(); return redirect()->route('admin.academy.challenges.edit', ['academyChallenge' => $academyChallenge])->with('success', 'Academy challenge updated.'); } public function challengesDestroy(AcademyChallenge $academyChallenge): RedirectResponse { $academyChallenge->delete(); $this->cache->clearAll(); return redirect()->route('admin.academy.challenges.index')->with('success', 'Academy challenge deleted.'); } public function badgesIndex(): Response { return $this->renderIndex('badges'); } public function badgesCreate(): Response { return $this->renderForm('badges', new AcademyBadge()); } public function badgesStore(UpsertAcademyBadgeRequest $request): RedirectResponse { $badge = new AcademyBadge(); $badge->fill($request->validated())->save(); return redirect()->route('admin.academy.badges.edit', ['academyBadge' => $badge])->with('success', 'Academy badge created.'); } public function badgesEdit(AcademyBadge $academyBadge): Response { return $this->renderForm('badges', $academyBadge); } public function badgesUpdate(UpsertAcademyBadgeRequest $request, AcademyBadge $academyBadge): RedirectResponse { $academyBadge->fill($request->validated())->save(); return redirect()->route('admin.academy.badges.edit', ['academyBadge' => $academyBadge])->with('success', 'Academy badge updated.'); } public function badgesDestroy(AcademyBadge $academyBadge): RedirectResponse { $academyBadge->delete(); return redirect()->route('admin.academy.badges.index')->with('success', 'Academy badge deleted.'); } public function submissionsIndex(Request $request): Response { $submissions = AcademyChallengeSubmission::query() ->with(['challenge', 'artwork', 'user']) ->latest('submitted_at') ->paginate(25) ->withQueryString(); $submissions->getCollection()->transform(fn (AcademyChallengeSubmission $submission): array => [ 'id' => (int) $submission->id, 'moderation_status' => (string) $submission->moderation_status, 'submitted_at' => $submission->submitted_at?->toISOString(), 'ai_tool_used' => (string) ($submission->ai_tool_used ?? ''), 'prompt_used' => (string) ($submission->prompt_used ?? ''), 'workflow_notes' => (string) ($submission->workflow_notes ?? ''), 'challenge' => $submission->challenge ? [ 'title' => (string) $submission->challenge->title, 'slug' => (string) $submission->challenge->slug, ] : null, 'user' => $submission->user ? [ 'name' => (string) $submission->user->name, 'username' => (string) ($submission->user->username ?? ''), ] : null, 'artwork' => $submission->artwork ? [ 'id' => (int) $submission->artwork->id, 'title' => (string) ($submission->artwork->title ?? 'Untitled artwork'), 'thumb_url' => $submission->artwork->thumbUrl('sm'), ] : null, 'approve_url' => route('admin.academy.submissions.approve', ['academyChallengeSubmission' => $submission]), 'reject_url' => route('admin.academy.submissions.reject', ['academyChallengeSubmission' => $submission]), ]); return Inertia::render('Admin/Academy/Submissions', [ 'submissions' => $submissions, ]); } public function approveSubmission(AcademyChallengeSubmission $academyChallengeSubmission): RedirectResponse { $academyChallengeSubmission->forceFill(['moderation_status' => 'approved'])->save(); return back()->with('success', 'Challenge submission approved.'); } public function rejectSubmission(AcademyChallengeSubmission $academyChallengeSubmission): RedirectResponse { $academyChallengeSubmission->forceFill(['moderation_status' => 'rejected'])->save(); return back()->with('success', 'Challenge submission rejected.'); } private function renderIndex(string $resource): Response { $meta = $this->resourceMeta($resource); $items = $meta['model']::query()->latest('updated_at')->paginate(25)->withQueryString(); $items->getCollection()->transform(fn (Model $model): array => $this->serializeIndexItem($resource, $model)); return Inertia::render('Admin/Academy/CrudIndex', [ 'resource' => $resource, 'title' => $meta['title'], 'subtitle' => $meta['subtitle'], 'items' => $items, 'columns' => $meta['columns'], 'createUrl' => route($meta['route_base'] . '.create'), ]); } private function renderForm(string $resource, Model $record): Response { $meta = $this->resourceMeta($resource); return Inertia::render('Admin/Academy/CrudForm', [ 'resource' => $resource, 'title' => $record->exists ? 'Edit ' . $meta['singular'] : 'Create ' . $meta['singular'], 'subtitle' => $meta['subtitle'], 'fields' => $meta['fields'], 'record' => $this->serializeFormRecord($resource, $record), 'submitUrl' => $record->exists ? route($meta['route_base'] . '.update', $this->routeParams($resource, $record)) : route($meta['route_base'] . '.store'), 'indexUrl' => route($meta['route_base'] . '.index'), 'destroyUrl' => $record->exists ? route($meta['route_base'] . '.destroy', $this->routeParams($resource, $record)) : null, 'method' => $record->exists ? 'patch' : 'post', ]); } private function resourceMeta(string $resource): array { return match ($resource) { 'categories' => [ 'model' => AcademyCategory::class, 'title' => 'Academy Categories', 'singular' => 'category', 'subtitle' => 'Manage lesson, prompt, pack, and challenge categories.', 'route_base' => 'admin.academy.categories', 'columns' => ['name', 'type', 'slug', 'active'], 'fields' => [ ['name' => 'type', 'label' => 'Type', 'type' => 'select', 'options' => [['value' => 'lesson', 'label' => 'Lesson'], ['value' => 'prompt', 'label' => 'Prompt'], ['value' => 'challenge', 'label' => 'Challenge'], ['value' => 'pack', 'label' => 'Pack']]], ['name' => 'name', 'label' => 'Name', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'description', 'label' => 'Description', 'type' => 'textarea'], ['name' => 'icon', 'label' => 'Icon', 'type' => 'text'], ['name' => 'order_num', 'label' => 'Order', 'type' => 'number'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], 'lessons' => [ 'model' => AcademyLesson::class, 'title' => 'Academy Lessons', 'singular' => 'lesson', 'subtitle' => 'Create and publish Academy lessons.', 'route_base' => 'admin.academy.lessons', 'columns' => ['title', 'difficulty', 'access_level', 'featured', 'active'], 'fields' => [ ['name' => 'category_id', 'label' => 'Category', 'type' => 'select', 'options' => $this->categoryOptions('lesson')], ['name' => 'title', 'label' => 'Title', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea'], ['name' => 'content', 'label' => 'Content', 'type' => 'textarea'], ['name' => 'difficulty', 'label' => 'Difficulty', 'type' => 'select', 'options' => $this->difficultyOptions()], ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->accessOptions()], ['name' => 'lesson_type', 'label' => 'Lesson Type', 'type' => 'text'], ['name' => 'cover_image', 'label' => 'Cover Image', 'type' => 'text'], ['name' => 'video_url', 'label' => 'Video URL', 'type' => 'text'], ['name' => 'reading_minutes', 'label' => 'Reading Minutes', 'type' => 'number'], ['name' => 'published_at', 'label' => 'Published At', 'type' => 'datetime-local'], ['name' => 'seo_title', 'label' => 'SEO Title', 'type' => 'text'], ['name' => 'seo_description', 'label' => 'SEO Description', 'type' => 'textarea'], ['name' => 'featured', 'label' => 'Featured', 'type' => 'checkbox'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], 'prompts' => [ 'model' => AcademyPromptTemplate::class, 'title' => 'Academy Prompt Templates', 'singular' => 'prompt template', 'subtitle' => 'Manage prompt previews, premium prompts, and prompt of the week.', 'route_base' => 'admin.academy.prompts', 'columns' => ['title', 'difficulty', 'access_level', 'prompt_of_week', 'active'], 'fields' => [ ['name' => 'category_id', 'label' => 'Category', 'type' => 'select', 'options' => $this->categoryOptions('prompt')], ['name' => 'title', 'label' => 'Title', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea'], ['name' => 'prompt', 'label' => 'Prompt', 'type' => 'textarea'], ['name' => 'negative_prompt', 'label' => 'Negative Prompt', 'type' => 'textarea'], ['name' => 'usage_notes', 'label' => 'Usage Notes', 'type' => 'textarea'], ['name' => 'workflow_notes', 'label' => 'Workflow Notes', 'type' => 'textarea'], ['name' => 'difficulty', 'label' => 'Difficulty', 'type' => 'select', 'options' => $this->difficultyOptions()], ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->accessOptions()], ['name' => 'aspect_ratio', 'label' => 'Aspect Ratio', 'type' => 'text'], ['name' => 'tags', 'label' => 'Tags', 'type' => 'csv'], ['name' => 'preview_image', 'label' => 'Preview Image', 'type' => 'text'], ['name' => 'published_at', 'label' => 'Published At', 'type' => 'datetime-local'], ['name' => 'seo_title', 'label' => 'SEO Title', 'type' => 'text'], ['name' => 'seo_description', 'label' => 'SEO Description', 'type' => 'textarea'], ['name' => 'featured', 'label' => 'Featured', 'type' => 'checkbox'], ['name' => 'prompt_of_week', 'label' => 'Prompt Of Week', 'type' => 'checkbox'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], 'packs' => [ 'model' => AcademyPromptPack::class, 'title' => 'Academy Prompt Packs', 'singular' => 'prompt pack', 'subtitle' => 'Bundle Academy prompts into reusable packs.', 'route_base' => 'admin.academy.packs', 'columns' => ['title', 'access_level', 'featured', 'active'], 'fields' => [ ['name' => 'title', 'label' => 'Title', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea'], ['name' => 'description', 'label' => 'Description', 'type' => 'textarea'], ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->accessOptions()], ['name' => 'one_time_price_cents', 'label' => 'One-time Price (cents)', 'type' => 'number'], ['name' => 'currency', 'label' => 'Currency', 'type' => 'text'], ['name' => 'cover_image', 'label' => 'Cover Image', 'type' => 'text'], ['name' => 'tags', 'label' => 'Tags', 'type' => 'csv'], ['name' => 'prompt_ids', 'label' => 'Prompt Templates', 'type' => 'multiselect', 'options' => $this->promptOptions()], ['name' => 'published_at', 'label' => 'Published At', 'type' => 'datetime-local'], ['name' => 'featured', 'label' => 'Featured', 'type' => 'checkbox'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], 'challenges' => [ 'model' => AcademyChallenge::class, 'title' => 'Academy Challenges', 'singular' => 'challenge', 'subtitle' => 'Create and moderate Academy challenge briefs.', 'route_base' => 'admin.academy.challenges', 'columns' => ['title', 'status', 'access_level', 'featured', 'active'], 'fields' => [ ['name' => 'title', 'label' => 'Title', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'excerpt', 'label' => 'Excerpt', 'type' => 'textarea'], ['name' => 'description', 'label' => 'Description', 'type' => 'textarea'], ['name' => 'brief', 'label' => 'Brief', 'type' => 'textarea'], ['name' => 'rules', 'label' => 'Rules', 'type' => 'textarea'], ['name' => 'access_level', 'label' => 'Access', 'type' => 'select', 'options' => $this->accessOptions()], ['name' => 'status', 'label' => 'Status', 'type' => 'select', 'options' => [['value' => 'draft', 'label' => 'Draft'], ['value' => 'scheduled', 'label' => 'Scheduled'], ['value' => 'active', 'label' => 'Active'], ['value' => 'voting', 'label' => 'Voting'], ['value' => 'completed', 'label' => 'Completed'], ['value' => 'archived', 'label' => 'Archived']]], ['name' => 'starts_at', 'label' => 'Starts At', 'type' => 'datetime-local'], ['name' => 'ends_at', 'label' => 'Ends At', 'type' => 'datetime-local'], ['name' => 'voting_starts_at', 'label' => 'Voting Starts At', 'type' => 'datetime-local'], ['name' => 'voting_ends_at', 'label' => 'Voting Ends At', 'type' => 'datetime-local'], ['name' => 'cover_image', 'label' => 'Cover Image', 'type' => 'text'], ['name' => 'prize_text', 'label' => 'Prize Text', 'type' => 'text'], ['name' => 'required_tags', 'label' => 'Required Tags', 'type' => 'csv'], ['name' => 'allowed_categories', 'label' => 'Allowed Categories', 'type' => 'csv'], ['name' => 'featured', 'label' => 'Featured', 'type' => 'checkbox'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], 'badges' => [ 'model' => AcademyBadge::class, 'title' => 'Academy Badges', 'singular' => 'badge', 'subtitle' => 'Define Academy plan and achievement badges.', 'route_base' => 'admin.academy.badges', 'columns' => ['name', 'badge_type', 'slug', 'active'], 'fields' => [ ['name' => 'name', 'label' => 'Name', 'type' => 'text'], ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], ['name' => 'description', 'label' => 'Description', 'type' => 'textarea'], ['name' => 'icon', 'label' => 'Icon', 'type' => 'text'], ['name' => 'badge_type', 'label' => 'Badge Type', 'type' => 'text'], ['name' => 'rules', 'label' => 'Rules JSON', 'type' => 'json'], ['name' => 'active', 'label' => 'Active', 'type' => 'checkbox'], ], ], default => throw new \InvalidArgumentException('Unknown Academy resource [' . $resource . '].'), }; } private function serializeIndexItem(string $resource, Model $model): array { return match ($resource) { 'categories' => [ 'id' => (int) $model->id, 'name' => (string) $model->name, 'type' => (string) $model->type, 'slug' => (string) $model->slug, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.categories.edit', ['academyCategory' => $model]), 'destroy_url' => route('admin.academy.categories.destroy', ['academyCategory' => $model]), ], 'lessons' => [ 'id' => (int) $model->id, 'title' => (string) $model->title, 'difficulty' => (string) $model->difficulty, 'access_level' => (string) $model->access_level, 'featured' => (bool) $model->featured, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.lessons.edit', ['academyLesson' => $model]), 'destroy_url' => route('admin.academy.lessons.destroy', ['academyLesson' => $model]), ], 'prompts' => [ 'id' => (int) $model->id, 'title' => (string) $model->title, 'difficulty' => (string) $model->difficulty, 'access_level' => (string) $model->access_level, 'prompt_of_week' => (bool) $model->prompt_of_week, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.prompts.edit', ['academyPromptTemplate' => $model]), 'destroy_url' => route('admin.academy.prompts.destroy', ['academyPromptTemplate' => $model]), ], 'packs' => [ 'id' => (int) $model->id, 'title' => (string) $model->title, 'access_level' => (string) $model->access_level, 'featured' => (bool) $model->featured, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.packs.edit', ['academyPromptPack' => $model]), 'destroy_url' => route('admin.academy.packs.destroy', ['academyPromptPack' => $model]), ], 'challenges' => [ 'id' => (int) $model->id, 'title' => (string) $model->title, 'status' => (string) $model->status, 'access_level' => (string) $model->access_level, 'featured' => (bool) $model->featured, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.challenges.edit', ['academyChallenge' => $model]), 'destroy_url' => route('admin.academy.challenges.destroy', ['academyChallenge' => $model]), ], 'badges' => [ 'id' => (int) $model->id, 'name' => (string) $model->name, 'badge_type' => (string) $model->badge_type, 'slug' => (string) $model->slug, 'active' => (bool) $model->active, 'edit_url' => route('admin.academy.badges.edit', ['academyBadge' => $model]), 'destroy_url' => route('admin.academy.badges.destroy', ['academyBadge' => $model]), ], default => [], }; } private function serializeFormRecord(string $resource, Model $record): array { return match ($resource) { 'categories' => [ 'type' => (string) ($record->type ?? 'lesson'), 'name' => (string) ($record->name ?? ''), 'slug' => (string) ($record->slug ?? ''), 'description' => (string) ($record->description ?? ''), 'icon' => (string) ($record->icon ?? ''), 'order_num' => (int) ($record->order_num ?? 0), 'active' => (bool) ($record->active ?? true), ], 'lessons' => [ 'category_id' => $record->category_id, 'title' => (string) ($record->title ?? ''), 'slug' => (string) ($record->slug ?? ''), 'excerpt' => (string) ($record->excerpt ?? ''), 'content' => (string) ($record->content ?? ''), 'difficulty' => (string) ($record->difficulty ?? 'beginner'), 'access_level' => (string) ($record->access_level ?? 'free'), 'lesson_type' => (string) ($record->lesson_type ?? 'article'), 'cover_image' => (string) ($record->cover_image ?? ''), 'video_url' => (string) ($record->video_url ?? ''), 'reading_minutes' => (int) ($record->reading_minutes ?? 5), 'published_at' => optional($record->published_at)?->format('Y-m-d\TH:i'), 'seo_title' => (string) ($record->seo_title ?? ''), 'seo_description' => (string) ($record->seo_description ?? ''), 'featured' => (bool) ($record->featured ?? false), 'active' => (bool) ($record->active ?? true), ], 'prompts' => [ 'category_id' => $record->category_id, 'title' => (string) ($record->title ?? ''), 'slug' => (string) ($record->slug ?? ''), 'excerpt' => (string) ($record->excerpt ?? ''), 'prompt' => (string) ($record->prompt ?? ''), 'negative_prompt' => (string) ($record->negative_prompt ?? ''), 'usage_notes' => (string) ($record->usage_notes ?? ''), 'workflow_notes' => (string) ($record->workflow_notes ?? ''), 'difficulty' => (string) ($record->difficulty ?? 'beginner'), 'access_level' => (string) ($record->access_level ?? 'free'), 'aspect_ratio' => (string) ($record->aspect_ratio ?? ''), 'tags' => implode(', ', (array) ($record->tags ?? [])), 'preview_image' => (string) ($record->preview_image ?? ''), 'published_at' => optional($record->published_at)?->format('Y-m-d\TH:i'), 'seo_title' => (string) ($record->seo_title ?? ''), 'seo_description' => (string) ($record->seo_description ?? ''), 'featured' => (bool) ($record->featured ?? false), 'prompt_of_week' => (bool) ($record->prompt_of_week ?? false), 'active' => (bool) ($record->active ?? true), ], 'packs' => [ 'title' => (string) ($record->title ?? ''), 'slug' => (string) ($record->slug ?? ''), 'excerpt' => (string) ($record->excerpt ?? ''), 'description' => (string) ($record->description ?? ''), 'access_level' => (string) ($record->access_level ?? 'creator'), 'one_time_price_cents' => $record->one_time_price_cents, 'currency' => (string) ($record->currency ?? 'EUR'), 'cover_image' => (string) ($record->cover_image ?? ''), 'tags' => implode(', ', (array) ($record->tags ?? [])), 'prompt_ids' => $record instanceof AcademyPromptPack ? $record->prompts->pluck('id')->all() : [], 'published_at' => optional($record->published_at)?->format('Y-m-d\TH:i'), 'featured' => (bool) ($record->featured ?? false), 'active' => (bool) ($record->active ?? true), ], 'challenges' => [ 'title' => (string) ($record->title ?? ''), 'slug' => (string) ($record->slug ?? ''), 'excerpt' => (string) ($record->excerpt ?? ''), 'description' => (string) ($record->description ?? ''), 'brief' => (string) ($record->brief ?? ''), 'rules' => (string) ($record->rules ?? ''), 'access_level' => (string) ($record->access_level ?? 'free'), 'status' => (string) ($record->status ?? 'draft'), 'starts_at' => optional($record->starts_at)?->format('Y-m-d\TH:i'), 'ends_at' => optional($record->ends_at)?->format('Y-m-d\TH:i'), 'voting_starts_at' => optional($record->voting_starts_at)?->format('Y-m-d\TH:i'), 'voting_ends_at' => optional($record->voting_ends_at)?->format('Y-m-d\TH:i'), 'cover_image' => (string) ($record->cover_image ?? ''), 'prize_text' => (string) ($record->prize_text ?? ''), 'required_tags' => implode(', ', (array) ($record->required_tags ?? [])), 'allowed_categories' => implode(', ', (array) ($record->allowed_categories ?? [])), 'featured' => (bool) ($record->featured ?? false), 'active' => (bool) ($record->active ?? true), ], 'badges' => [ 'name' => (string) ($record->name ?? ''), 'slug' => (string) ($record->slug ?? ''), 'description' => (string) ($record->description ?? ''), 'icon' => (string) ($record->icon ?? ''), 'badge_type' => (string) ($record->badge_type ?? 'achievement'), 'rules' => json_encode((array) ($record->rules ?? []), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), 'active' => (bool) ($record->active ?? true), ], default => [], }; } private function routeParams(string $resource, Model $record): array { return match ($resource) { 'categories' => ['academyCategory' => $record], 'lessons' => ['academyLesson' => $record], 'prompts' => ['academyPromptTemplate' => $record], 'packs' => ['academyPromptPack' => $record], 'challenges' => ['academyChallenge' => $record], 'badges' => ['academyBadge' => $record], default => [], }; } private function categoryOptions(string $type): array { return AcademyCategory::query() ->where('type', $type) ->orderBy('order_num') ->orderBy('name') ->get() ->map(fn (AcademyCategory $category): array => ['value' => $category->id, 'label' => $category->name]) ->prepend(['value' => '', 'label' => 'No category']) ->values() ->all(); } private function promptOptions(): array { return AcademyPromptTemplate::query() ->orderBy('title') ->get() ->map(fn (AcademyPromptTemplate $prompt): array => ['value' => $prompt->id, 'label' => $prompt->title]) ->values() ->all(); } private function difficultyOptions(): array { return collect((array) config('academy.difficulty_levels', [])) ->map(fn (string $value): array => ['value' => $value, 'label' => ucfirst($value)]) ->values() ->all(); } private function accessOptions(): array { return [ ['value' => 'free', 'label' => 'Free'], ['value' => 'creator', 'label' => 'Creator'], ['value' => 'pro', 'label' => 'Pro'], ]; } private function syncPackItems(AcademyPromptPack $pack, array $promptIds): void { AcademyPromptPackItem::query()->where('pack_id', $pack->id)->delete(); foreach (array_values($promptIds) as $index => $promptId) { AcademyPromptPackItem::query()->create([ 'pack_id' => $pack->id, 'prompt_template_id' => (int) $promptId, 'order_num' => $index, ]); } } }