validate([ 'q' => ['nullable', 'string', 'max:120'], 'sector' => ['nullable', 'string', 'max:80'], 'province' => ['nullable', 'string', 'max:80'], 'province_id' => ['nullable', 'uuid'], 'district_id' => ['nullable', 'uuid'], 'admin_post_id' => ['nullable', 'uuid'], 'locality_id' => ['nullable', 'uuid'], 'geo_location_id' => ['nullable', 'uuid'], 'maturity_level' => ['nullable', 'integer', 'between:1,6'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:50'], 'min_amount' => ['nullable', 'numeric', 'min:0', 'max:999999999999'], 'max_amount' => ['nullable', 'numeric', 'min:0', 'max:999999999999', ...($request->filled('min_amount') ? ['gte:min_amount'] : [])], 'currency' => ['nullable', 'required_with:min_amount,max_amount', 'in:MZN,USD,EUR,ZAR'], ]); $query = Opportunity::with('sector:id,name,slug')->published(); foreach (['min_amount' => '>=', 'max_amount' => '<='] as $field => $operator) { if (isset($validated[$field])) { $query->where('estimated_investment_amount', $operator, $validated[$field]); } } if (! empty($validated['currency'])) { $query->where('estimated_investment_currency', $validated['currency']); } if (! empty($validated['q'])) { app(OpportunityTextSearch::class)->constrain($query, $validated['q']); } if (! empty($validated['sector'])) { $sector = $validated['sector']; SectorOrganization::constrain($query, $sector); } $territory = array_intersect_key($validated, array_flip(['province', 'province_id', 'district_id', 'admin_post_id', 'locality_id', 'geo_location_id'])); if (array_filter($territory, fn ($value) => $value !== null && $value !== '') !== []) { // Keep the legacy search's sorting and non-territorial filters while sharing the public territorial contract. $query->whereIn('opportunities.id', app(InvestmentCatalogue::class)->query($territory)->reorder()->select('opportunities.id')); } if (! empty($validated['maturity_level'])) { OpportunityMaturity::constrainSearch($query, 'historical:'.$validated['maturity_level']); } $results = $query->orderByDesc('is_featured') ->orderByDesc('published_at') ->paginate($validated['per_page'] ?? 12); app(PublicSearchMetrics::class)->candidate($request, 'opportunities_api', $results->total(), $validated); return response()->json($results->withQueryString()->through(fn ($record) => app(InvestmentCatalogue::class)->presentLocation($record))); } public function show(string $slug): JsonResponse { $opportunity = Opportunity::with('sector:id,name,slug') ->where('slug', $slug)->published()->firstOrFail(); return response()->json(['success' => true, 'data' => $opportunity]); } }