isAdmin()) { return true; } return $this->rankForUser($user) >= $this->rankForLevel($accessLevel); } public function canAccessLesson(?User $user, AcademyLesson $lesson): bool { return $this->canAccessContent($user, (string) $lesson->access_level); } public function canAccessPrompt(?User $user, AcademyPromptTemplate $prompt): bool { return $this->canAccessContent($user, (string) $prompt->access_level); } public function canAccessPack(?User $user, AcademyPromptPack $pack): bool { return $this->canAccessContent($user, (string) $pack->access_level); } public function canAccessChallenge(?User $user, AcademyChallenge $challenge): bool { return $this->canAccessContent($user, (string) $challenge->access_level); } public function lessonPayload(AcademyLesson $lesson, ?User $viewer, bool $includeFull = false): array { $authorized = $this->canAccessLesson($viewer, $lesson); $fullContent = (string) ($lesson->content ?? ''); return [ 'id' => (int) $lesson->id, 'title' => (string) $lesson->title, 'slug' => (string) $lesson->slug, 'excerpt' => (string) ($lesson->excerpt ?? ''), 'content' => ($authorized && $includeFull) ? $fullContent : null, 'content_preview' => $authorized ? null : $this->previewText($fullContent, 360), 'difficulty' => (string) $lesson->difficulty, 'access_level' => (string) $lesson->access_level, 'lesson_type' => (string) $lesson->lesson_type, 'cover_image' => $lesson->cover_image, 'video_url' => $authorized ? $lesson->video_url : null, 'reading_minutes' => (int) $lesson->reading_minutes, 'featured' => (bool) $lesson->featured, 'active' => (bool) $lesson->active, 'published_at' => $lesson->published_at?->toISOString(), 'category' => $lesson->category ? [ 'id' => (int) $lesson->category->id, 'name' => (string) $lesson->category->name, 'slug' => (string) $lesson->category->slug, ] : null, 'locked' => ! $authorized, 'can_access' => $authorized, ]; } public function promptPayload(AcademyPromptTemplate $prompt, ?User $viewer, bool $includeFull = false): array { $authorized = $this->canAccessPrompt($viewer, $prompt); return [ 'id' => (int) $prompt->id, 'title' => (string) $prompt->title, 'slug' => (string) $prompt->slug, 'excerpt' => (string) ($prompt->excerpt ?? ''), 'prompt' => ($authorized && $includeFull) ? (string) $prompt->prompt : null, 'negative_prompt' => ($authorized && $includeFull) ? (string) ($prompt->negative_prompt ?? '') : null, 'usage_notes' => ($authorized && $includeFull) ? (string) ($prompt->usage_notes ?? '') : null, 'workflow_notes' => ($authorized && $includeFull) ? (string) ($prompt->workflow_notes ?? '') : null, 'prompt_preview' => $authorized ? null : $this->previewText((string) $prompt->prompt, 220), 'difficulty' => (string) $prompt->difficulty, 'access_level' => (string) $prompt->access_level, 'aspect_ratio' => $prompt->aspect_ratio, 'tags' => array_values((array) ($prompt->tags ?? [])), 'tool_notes' => $authorized ? (array) ($prompt->tool_notes ?? []) : [], 'preview_image' => $prompt->preview_image, 'featured' => (bool) $prompt->featured, 'prompt_of_week' => (bool) $prompt->prompt_of_week, 'published_at' => $prompt->published_at?->toISOString(), 'category' => $prompt->category ? [ 'id' => (int) $prompt->category->id, 'name' => (string) $prompt->category->name, 'slug' => (string) $prompt->category->slug, ] : null, 'locked' => ! $authorized, 'can_access' => $authorized, ]; } public function packPayload(AcademyPromptPack $pack, ?User $viewer, bool $includePrompts = false): array { $authorized = $this->canAccessPack($viewer, $pack); return [ 'id' => (int) $pack->id, 'title' => (string) $pack->title, 'slug' => (string) $pack->slug, 'excerpt' => (string) ($pack->excerpt ?? ''), 'description' => (string) ($pack->description ?? ''), 'access_level' => (string) $pack->access_level, 'cover_image' => $pack->cover_image, 'tags' => array_values((array) ($pack->tags ?? [])), 'featured' => (bool) $pack->featured, 'published_at' => $pack->published_at?->toISOString(), 'locked' => ! $authorized, 'can_access' => $authorized, 'prompts' => $includePrompts ? $pack->prompts->map(fn (AcademyPromptTemplate $prompt): array => $this->promptPayload($prompt, $viewer, $authorized))->values()->all() : [], ]; } public function challengePayload(AcademyChallenge $challenge, ?User $viewer, bool $includeSubmissions = false): array { $authorized = $this->canAccessChallenge($viewer, $challenge); return [ 'id' => (int) $challenge->id, 'title' => (string) $challenge->title, 'slug' => (string) $challenge->slug, 'excerpt' => (string) ($challenge->excerpt ?? ''), 'description' => (string) ($challenge->description ?? ''), 'brief' => (string) ($challenge->brief ?? ''), 'rules' => (string) ($challenge->rules ?? ''), 'access_level' => (string) $challenge->access_level, 'status' => (string) $challenge->status, 'starts_at' => $challenge->starts_at?->toISOString(), 'ends_at' => $challenge->ends_at?->toISOString(), 'voting_starts_at' => $challenge->voting_starts_at?->toISOString(), 'voting_ends_at' => $challenge->voting_ends_at?->toISOString(), 'cover_image' => $challenge->cover_image, 'prize_text' => $challenge->prize_text, 'required_tags' => array_values((array) ($challenge->required_tags ?? [])), 'allowed_categories' => array_values((array) ($challenge->allowed_categories ?? [])), 'featured' => (bool) $challenge->featured, 'locked' => ! $authorized, 'can_access' => $authorized, 'submission_count' => $includeSubmissions ? (int) $challenge->submissions()->approved()->count() : null, ]; } private function rankForUser(User $user): int { if (method_exists($user, 'hasAcademyProAccess') && $user->hasAcademyProAccess()) { return $this->rankForLevel('pro'); } if (method_exists($user, 'hasAcademyCreatorAccess') && $user->hasAcademyCreatorAccess()) { return $this->rankForLevel('creator'); } return $this->rankForLevel('free'); } private function rankForLevel(string $accessLevel): int { return match (Str::lower(trim($accessLevel))) { 'admin' => 99, 'pro' => 30, 'creator' => 20, default => 10, }; } private function previewText(string $value, int $limit): string { $plain = trim(strip_tags($value)); if ($plain === '') { return ''; } $length = mb_strlen($plain); $previewLength = min($limit, max(12, (int) ceil($length * 0.55))); if ($previewLength >= $length) { $previewLength = max(1, $length - 1); } return rtrim(mb_substr($plain, 0, $previewLength)) . '...'; } }