buildSearchQuery($search); return $query->cursorPaginate($perPage); } /** * Get popular keywords for filtering */ public function getPopularKeywords(int $limit = 20): SupportCollection { $cacheKey = "popular_keywords_limit_{$limit}"; return Cache::remember($cacheKey, 60 * 60 * 24, function () use ($limit) { return MemeMedia::where('is_enabled', true) ->get() ->pluck('keywords') ->flatten() ->countBy() ->sort() ->reverse() ->take($limit) ->keys(); }); } /** * Get available meme types for filtering */ public function getAvailableTypes(): SupportCollection { return MemeMedia::where('is_enabled', true) ->distinct() ->pluck('type') ->filter(); } /** * Find meme by slug */ public function findBySlug(string $slug): MemeMedia { return MemeMedia::where('slug', $slug) ->where('is_enabled', true) ->firstOrFail(); } /** * Find meme by hashids */ public function findByHashIds(string $ids): MemeMedia { $memeId = hashids_decode($ids); if (!$memeId) { throw new ModelNotFoundException('Meme not found'); } return MemeMedia::where('id', $memeId) ->where('is_enabled', true) ->firstOrFail(); } /** * Get related memes based on keywords */ public function getRelatedMemes(MemeMedia $meme, int $limit = 6): Collection { $relatedMemes = MemeMedia::where('is_enabled', true) ->where('id', '!=', $meme->id) ->where(function ($query) use ($meme) { if ($meme->keywords) { foreach ($meme->keywords as $keyword) { $query->orWhereJsonContains('keywords', $keyword) ->orWhereJsonContains('action_keywords', $keyword) ->orWhereJsonContains('emotion_keywords', $keyword) ->orWhereJsonContains('misc_keywords', $keyword); } } }) ->limit($limit) ->get(); // If we have less than the desired limit, fill up with random ones if ($relatedMemes->count() < $limit) { $excludeIds = $relatedMemes->pluck('id')->push($meme->id)->toArray(); $needed = $limit - $relatedMemes->count(); $randomMemes = $this->fillWithRandomMemes($relatedMemes, $limit, $excludeIds); $relatedMemes = $relatedMemes->merge($randomMemes); } return $relatedMemes; } /** * Fill collection with random memes up to target count */ public function fillWithRandomMemes(Collection $existing, int $targetCount, array $excludeIds): Collection { $needed = $targetCount - $existing->count(); if ($needed <= 0) { return collect(); } return MemeMedia::where('is_enabled', true) ->whereNotIn('id', $excludeIds) ->inRandomOrder() ->limit($needed) ->get(); } /** * Build search query with keyword matching */ private function buildSearchQuery(?string $search = null): Builder { $query = $this->getEnabledMemesQuery(); if ($search) { $query->where(function ($q) use ($search) { $q->where('name', 'ilike', "%{$search}%") ->orWhere('description', 'ilike', "%{$search}%") ->orWhereJsonContains('keywords', $search) ->orWhereJsonContains('action_keywords', $search) ->orWhereJsonContains('emotion_keywords', $search) ->orWhereJsonContains('misc_keywords', $search); }); } return $query; } /** * Get all enabled memes for sitemap generation */ public function getAllEnabledMemes(): Collection { return MemeMedia::where('is_enabled', true) ->orderBy('updated_at', 'desc') ->get(); } /** * Get base query for enabled memes */ private function getEnabledMemesQuery(): Builder { return MemeMedia::query() ->where('is_enabled', true) ->orderBy('id', 'desc'); } }