cacheService->searchResultsKey($filters, (int) $page); $cached = $this->cacheService->remember($cacheKey, CacheService::CACHE_SEARCH_RESULTS, function () use ($filters, $perPage) { $query = Job::query() ->with(['source', 'category', 'jobLocation']) ->active(); $this->applyFilters($query, $filters); $this->applySorting($query, $filters['sort'] ?? 'date'); return $query->paginate($perPage); }); return $cached; } /** * Apply filters to the query */ private function applyFilters(Builder $query, array $filters): void { // Keyword search (title, company, description) if (!empty($filters['keywords'])) { $keywords = $filters['keywords']; $query->where(function ($q) use ($keywords) { $q->where('title', 'LIKE', "%{$keywords}%") ->orWhere('company', 'LIKE', "%{$keywords}%") ->orWhere('description', 'LIKE', "%{$keywords}%"); }); } // Full-text search (if supported) if (!empty($filters['search'])) { $search = $filters['search']; $query->whereRaw( "MATCH(title, company, description) AGAINST(? IN BOOLEAN MODE)", [$search] ); } // Location filter if (!empty($filters['location'])) { $location = $filters['location']; $query->where(function ($q) use ($location) { $q->where('location', 'LIKE', "%{$location}%") ->orWhereHas('jobLocation', function ($q2) use ($location) { $q2->where('city', 'LIKE', "%{$location}%") ->orWhere('state', 'LIKE', "%{$location}%") ->orWhere('country', 'LIKE', "%{$location}%"); }); }); } // Category filter if (!empty($filters['category_id'])) { $query->where('job_category_id', $filters['category_id']); } // Job type filter (can be array for multiple types) if (!empty($filters['job_type'])) { $types = is_array($filters['job_type']) ? $filters['job_type'] : [$filters['job_type']]; $query->whereIn('job_type', $types); } // Experience level filter (can be array) if (!empty($filters['experience_level'])) { $levels = is_array($filters['experience_level']) ? $filters['experience_level'] : [$filters['experience_level']]; $query->whereIn('experience_level', $levels); } // Remote filter if (isset($filters['remote']) && $filters['remote'] !== '') { $query->where('remote_allowed', (bool) $filters['remote']); } // Salary range filter if (!empty($filters['salary_min'])) { $query->where(function ($q) use ($filters) { $q->where('salary_max', '>=', $filters['salary_min']) ->orWhereNull('salary_max'); }); } if (!empty($filters['salary_max'])) { $query->where(function ($q) use ($filters) { $q->where('salary_min', '<=', $filters['salary_max']) ->orWhereNull('salary_min'); }); } // Posted date filter (e.g., last 24 hours, 7 days, 30 days) if (!empty($filters['posted_within'])) { $days = (int) $filters['posted_within']; $query->where(function ($q) use ($days) { $q->where('posted_at', '>=', now()->subDays($days)) ->orWhere(function ($q2) use ($days) { $q2->whereNull('posted_at') ->where('created_at', '>=', now()->subDays($days)); }); }); } // Source filter if (!empty($filters['source_id'])) { $query->where('job_source_id', $filters['source_id']); } // Company filter if (!empty($filters['company'])) { $query->where('company', 'LIKE', "%{$filters['company']}%"); } } /** * Apply sorting to the query */ private function applySorting(Builder $query, string $sort): void { match ($sort) { 'date' => $query->latest('posted_at'), 'date_asc' => $query->oldest('posted_at'), 'salary_desc' => $query->orderBy('salary_max', 'desc')->orderBy('salary_min', 'desc'), 'salary_asc' => $query->orderBy('salary_min', 'asc')->orderBy('salary_max', 'asc'), 'title' => $query->orderBy('title', 'asc'), 'company' => $query->orderBy('company', 'asc'), 'relevance' => $query->latest('created_at'), // Default to newest if no full-text score default => $query->latest('posted_at'), }; } /** * Get autocomplete suggestions for job titles */ public function getTitleSuggestions(string $query, int $limit = 10): array { return Job::select('title') ->where('title', 'LIKE', "{$query}%") ->active() ->groupBy('title') ->limit($limit) ->pluck('title') ->toArray(); } /** * Get autocomplete suggestions for companies */ public function getCompanySuggestions(string $query, int $limit = 10): array { return Job::select('company') ->where('company', 'LIKE', "{$query}%") ->active() ->groupBy('company') ->limit($limit) ->pluck('company') ->toArray(); } /** * Get autocomplete suggestions for locations */ public function getLocationSuggestions(string $query, int $limit = 10): array { return Job::select('location') ->where('location', 'LIKE', "{$query}%") ->whereNotNull('location') ->active() ->groupBy('location') ->limit($limit) ->pluck('location') ->toArray(); } /** * Get available filter options */ public function getFilterOptions(): array { $cacheKey = $this->cacheService->filterOptionsKey(); return $this->cacheService->remember($cacheKey, CacheService::CACHE_FILTER_OPTIONS, function () { // Get actual distinct values from database for job types and experience levels $jobTypes = Job::select('job_type') ->whereNotNull('job_type') ->distinct() ->pluck('job_type') ->toArray(); $experienceLevels = Job::select('experience_level') ->whereNotNull('experience_level') ->distinct() ->pluck('experience_level') ->toArray(); return [ 'job_types' => $jobTypes, 'experience_levels' => $experienceLevels, 'posted_within' => [ 1 => 'Last 24 hours', 7 => 'Last 7 days', 30 => 'Last 30 days', 90 => 'Last 3 months', ], 'sort_options' => [ 'date' => 'Most Recent', 'date_asc' => 'Oldest First', 'salary_desc' => 'Highest Salary', 'salary_asc' => 'Lowest Salary', 'title' => 'Title A-Z', 'company' => 'Company A-Z', ], ]; }); } }