21268ab477
Switch from legacy Places textsearch and Directions APIs (disabled on this project) to Places API (New) and Routes API (New). Both places.py and the assistant's _places_search helper updated. Also fixes uid() recursive self-call in trips page and adds Places API response logging.
101 lines
3.4 KiB
Python
101 lines
3.4 KiB
Python
import httpx
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from app.config import settings
|
|
from app.internal.logger import logger
|
|
|
|
router = APIRouter(prefix="/places", tags=["places"])
|
|
|
|
_PLACES_SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"
|
|
_ROUTES_URL = "https://routes.googleapis.com/directions/v2:computeRoutes"
|
|
_PLACES_FIELDS = "places.id,places.displayName,places.formattedAddress,places.rating,places.googleMapsUri,places.location"
|
|
|
|
|
|
@router.get("/search")
|
|
async def search_places(query: str = Query(...), near: str = Query("")):
|
|
if not settings.google_maps_api_key:
|
|
raise HTTPException(503, "Google Maps API not configured.")
|
|
|
|
full_query = f"{query} {near}".strip()
|
|
try:
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
r = await client.post(
|
|
_PLACES_SEARCH_URL,
|
|
json={"textQuery": full_query},
|
|
headers={
|
|
"X-Goog-Api-Key": settings.google_maps_api_key,
|
|
"X-Goog-FieldMask": _PLACES_FIELDS,
|
|
},
|
|
)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
except Exception as e:
|
|
logger.error(f"Places search failed: {e}")
|
|
raise HTTPException(502, "Places search failed.")
|
|
|
|
return [
|
|
{
|
|
"name": p.get("displayName", {}).get("text"),
|
|
"address": p.get("formattedAddress"),
|
|
"place_id": p.get("id"),
|
|
"lat": p.get("location", {}).get("latitude"),
|
|
"lng": p.get("location", {}).get("longitude"),
|
|
"maps_link": p.get("googleMapsUri"),
|
|
"rating": p.get("rating"),
|
|
}
|
|
for p in data.get("places", [])[:6]
|
|
]
|
|
|
|
|
|
@router.get("/directions")
|
|
async def get_directions(
|
|
origin: str = Query(...),
|
|
destination: str = Query(...),
|
|
):
|
|
if not settings.google_maps_api_key:
|
|
raise HTTPException(503, "Google Maps API not configured.")
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
r = await client.post(
|
|
_ROUTES_URL,
|
|
json={
|
|
"origin": {"address": origin},
|
|
"destination": {"address": destination},
|
|
"travelMode": "DRIVE",
|
|
},
|
|
headers={
|
|
"X-Goog-Api-Key": settings.google_maps_api_key,
|
|
"X-Goog-FieldMask": "routes.duration,routes.distanceMeters",
|
|
},
|
|
)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
except Exception as e:
|
|
logger.error(f"Directions failed: {e}")
|
|
raise HTTPException(502, "Directions request failed.")
|
|
|
|
routes = data.get("routes", [])
|
|
if not routes:
|
|
return {"duration_text": None, "duration_seconds": None, "distance_text": None}
|
|
|
|
route = routes[0]
|
|
duration_seconds = int(route.get("duration", "0s").rstrip("s") or 0)
|
|
distance_m = route.get("distanceMeters", 0)
|
|
|
|
# Format human-readable strings
|
|
hours, rem = divmod(duration_seconds, 3600)
|
|
mins = rem // 60
|
|
if hours:
|
|
duration_text = f"{hours} hr {mins} min" if mins else f"{hours} hr"
|
|
else:
|
|
duration_text = f"{mins} min"
|
|
|
|
miles = distance_m / 1609.34
|
|
distance_text = f"{miles:.1f} mi"
|
|
|
|
return {
|
|
"duration_text": duration_text,
|
|
"duration_seconds": duration_seconds,
|
|
"distance_text": distance_text,
|
|
}
|