fox/app/Http/Controllers/FoxPlaceInfoController.php

110 lines
4.5 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* "Wie schaut der erste Bezirk aus?" / "Erzähl mir was über das Belvedere" →
* Google Places (New) Text Search + Photo-Fetch. Liefert dem HUD ein Detail-Panel
* mit Bild + Adresse + Bewertung + Öffnungszeiten + Website.
*/
class FoxPlaceInfoController
{
public function __invoke(Request $request): JsonResponse
{
$request->validate([
'query' => 'required|string|max:200',
'bias_lat' => 'nullable|numeric',
'bias_lng' => 'nullable|numeric',
]);
$key = config('services.google.maps_key');
if (empty($key)) {
return response()->json(['error' => 'no_key'], 503);
}
$query = $request->string('query')->toString();
$lat = $request->filled('bias_lat') ? (float) $request->input('bias_lat') : null;
$lng = $request->filled('bias_lng') ? (float) $request->input('bias_lng') : null;
try {
$body = [
'textQuery' => $query,
'languageCode' => 'de',
'regionCode' => 'AT',
'maxResultCount' => 1,
];
if ($lat !== null && $lng !== null) {
$body['locationBias'] = [
'circle' => [
'center' => ['latitude' => $lat, 'longitude' => $lng],
'radius' => 30_000.0,
],
];
}
$response = Http::timeout(10)
->withHeaders([
'Content-Type' => 'application/json',
'X-Goog-Api-Key' => $key,
'X-Goog-FieldMask' => 'places.id,places.displayName,places.formattedAddress,'
.'places.location,places.rating,places.userRatingCount,places.websiteUri,'
.'places.regularOpeningHours,places.types,places.editorialSummary,'
.'places.photos,places.priceLevel,'
.'places.nationalPhoneNumber,places.internationalPhoneNumber',
])
->post('https://places.googleapis.com/v1/places:searchText', $body);
if (! $response->successful()) {
Log::warning('Place-Info HTTP-Fehler', ['status' => $response->status()]);
return response()->json(['error' => 'api_error'], 502);
}
$place = $response->json('places.0');
if (! $place) {
return response()->json(['error' => 'not_found'], 404);
}
$photoUrl = null;
if (! empty($place['photos'][0]['name'])) {
// Photo URL: /v1/{photo_resource}/media?maxWidthPx=...&key=...
$photoUrl = 'https://places.googleapis.com/v1/'.$place['photos'][0]['name'].'/media?maxWidthPx=800&key='.urlencode($key);
}
$photoUrls = [];
foreach (array_slice($place['photos'] ?? [], 0, 5) as $photo) {
if (!empty($photo['name'])) {
$photoUrls[] = 'https://places.googleapis.com/v1/'.$photo['name'].'/media?maxWidthPx=800&key='.urlencode($key);
}
}
return response()->json([
'name' => $place['displayName']['text'] ?? null,
'address' => $place['formattedAddress'] ?? '',
'lat' => $place['location']['latitude'] ?? null,
'lng' => $place['location']['longitude'] ?? null,
'rating' => $place['rating'] ?? null,
'rating_count' => $place['userRatingCount'] ?? null,
'website' => $place['websiteUri'] ?? null,
'summary' => $place['editorialSummary']['text'] ?? null,
'opening_hours' => $place['regularOpeningHours']['weekdayDescriptions'] ?? null,
'types' => $place['types'] ?? [],
'photo_url' => $photoUrl,
'price_level' => $place['priceLevel'] ?? null,
'place_id' => $place['id'] ?? null,
'phone' => $place['nationalPhoneNumber'] ?? $place['internationalPhoneNumber'] ?? null,
'photo_urls' => $photoUrls,
]);
} catch (\Throwable $e) {
Log::warning('Place-Info exception', ['e' => $e->getMessage()]);
return response()->json(['error' => 'exception'], 500);
}
}
}