validate([ 'q' => 'required|string|min:2|max:255', ]); $query = $validated['q']; // Add to search history if session is available (for web routes) if ($request->hasSession()) { $this->addToSearchHistory($request, $query); } // Search in local database first $localResults = $this->searchLocalCities($query); $wikipediaResults = null; // If no local results, fetch from Wikipedia and create new city if ($localResults->isEmpty()) { $newCity = $this->createCityFromWikipedia($query, $wikipediaService); if ($newCity) { $localResults = collect([$newCity]); } } $response = [ 'local' => CityResource::collection($localResults), 'wikipedia' => $wikipediaResults, ]; // Add recent searches if session is available if ($request->hasSession()) { $response['recent_searches'] = $request->session()->get('search_history', []); } return response()->json($response); } /** * Add search query to session history * Note: This method should only be called when hasSession() is true */ private function addToSearchHistory(Request $request, string $query): void { if (!$request->hasSession()) { return; } $searchHistory = $request->session()->get('search_history', []); // Remove duplicate if exists $searchHistory = array_filter($searchHistory, function($item) use ($query) { return strtolower($item) !== strtolower($query); }); // Add new search to the beginning array_unshift($searchHistory, $query); // Keep only last 10 searches $searchHistory = array_slice($searchHistory, 0, 10); $request->session()->put('search_history', $searchHistory); } /** * Search for cities in local database */ private function searchLocalCities(string $query) { return City::where('name', 'LIKE', "%{$query}%") ->orWhere('country', 'LIKE', "%{$query}%") ->limit(10) ->get(); } /** * Create a city from Wikipedia data */ private function createCityFromWikipedia(string $query, WikipediaService $wikipediaService) { try { $wikipediaData = $wikipediaService->getCitySummary($query); if (!$wikipediaData) { return null; } $countryName = $this->fetchCountryName($wikipediaData['title']); $city = City::create([ 'name' => $wikipediaData['title'], 'country' => $countryName, 'latitude' => $wikipediaData['latitude'] ?? null, 'longitude' => $wikipediaData['longitude'] ?? null, 'wikipedia_summary' => $wikipediaData['summary'] ?? null, 'wikipedia_url' => $wikipediaData['url'] ?? null, 'thumbnail_url' => $wikipediaData['thumbnail'] ?? null, 'image_url' => $wikipediaData['image'] ?? null, 'wikipedia_page_id' => $wikipediaData['page_id'] ?? null, 'wikipedia_cached_at' => now(), ]); // Dispatch job to fetch attractions for the newly created city if ($city && $city->latitude && $city->longitude) { \App\Jobs\FetchAttractionsJob::dispatch($city); } return $city; } catch (\Exception $e) { \Log::error('Failed to create city from Wikipedia', [ 'query' => $query, 'error' => $e->getMessage(), ]); return null; } } /** * Fetch country name from external API */ private function fetchCountryName(string $cityName) { try { $response = Http::post('https://countriesnow.space/api/v0.1/countries/population/cities', [ 'city' => $cityName, ]); if ($response->successful()) { $data = $response->json(); return $data['data']['country'] ?? 'Unknown'; } return 'Unknown'; } catch (\Exception $e) { \Log::error('Failed to fetch country name', [ 'city' => $cityName, 'error' => $e->getMessage(), ]); return 'Unknown'; } } /** * Display the specified city */ public function show(City $city) { $city->loadCount('attractions'); // fetch attractions, or dispatch a job to fetch them return new CityResource($city); } /** * Get attractions for a city with pagination */ public function attractions(Request $request, City $city) { $validated = $request->validate([ 'category' => 'nullable|string|max:50', 'limit' => 'nullable|integer|min:1|max:100', 'page' => 'nullable|integer|min:1', ]); $query = $city->attractions() ->select(['id', 'city_id', 'name', 'description', 'latitude', 'longitude', 'category', 'rating', 'image_url']) ->orderBy('name', 'asc'); // Filter by category if provided if ($request->has('category') && $request->category) { $query->where('category', $request->category); } // Use pagination for better performance $perPage = $request->input('limit', 20); $attractions = $query->paginate($perPage); return AttractionResource::collection($attractions); } /** * Get map data for a city with optimized query */ public function mapData(City $city) { // Eager load only necessary columns for map display $city->load(['attractions' => function ($query) { $query->select(['id', 'city_id', 'name', 'latitude', 'longitude', 'category']); }]); return response()->json([ 'city' => [ 'name' => $city->name, 'latitude' => (float) $city->latitude, 'longitude' => (float) $city->longitude, ], 'attractions' => $city->attractions->map(function ($attraction) { return [ 'id' => $attraction->id, 'name' => $attraction->name, 'latitude' => (float) $attraction->latitude, 'longitude' => (float) $attraction->longitude, 'category' => $attraction->category, ]; }), ]); } /** * Add a new city (Protected) */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'country' => 'required|string|max:255', 'latitude' => 'nullable|numeric|between:-90,90', 'longitude' => 'nullable|numeric|between:-180,180', ]); $city = City::create($validated); // Dispatch job to fetch data from Wikipedia FetchCityDataJob::dispatch($city); return new CityResource($city); } }