guideCity/backend/app/api/routes/nearby.py
vrubelroman be67902b45 Add structured logging throughout the backend
Request-level logging middleware (method/path/status/duration),
per-module loggers in routes/services (nearby search radius
expansion, 404s, name search), LOG_LEVEL setting wired through
docker-compose, and seed_loader converted from print() to logging.

Verified: pytest passes, and manually confirmed log lines appear for
both successful and 404 requests via `docker compose logs api`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 20:10:10 +00:00

74 lines
2.5 KiB
Python

import logging
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import lang_param, length_param
from app.db.session import get_db
from app.schemas.common import LatLon
from app.schemas.nearby import NearbyPlace, NearbyResponse
from app.schemas.place import ContentOut
from app.services.nearby_search import find_nearby
logger = logging.getLogger("guidecity.nearby")
router = APIRouter(tags=["nearby"])
@router.get("/nearby", response_model=NearbyResponse)
def nearby(
lat: float = Query(),
lon: float = Query(),
city_slug: str | None = Query(default=None),
lang: str = Depends(lang_param),
length: str = Depends(length_param),
min_results: int = Query(default=5, ge=1, le=50),
initial_radius_m: int = Query(default=300, ge=50, le=5000),
step_m: int = Query(default=200, ge=50, le=5000),
max_radius_m: int = Query(default=5000, ge=100, le=20000),
speed: float | None = Query(
default=None,
description="Reserved for v2: user's speed in m/s. Currently accepted but ignored.",
),
heading: float | None = Query(
default=None,
description="Reserved for v2: user's heading in degrees. Currently accepted but ignored.",
),
db: Session = Depends(get_db),
) -> NearbyResponse:
if speed is not None or heading is not None:
logger.debug("nearby called with reserved v2 params speed=%s heading=%s (ignored)", speed, heading)
result = find_nearby(
db,
lat=lat,
lon=lon,
language=lang,
length=length,
city_slug=city_slug,
min_results=min_results,
initial_radius_m=initial_radius_m,
step_m=step_m,
max_radius_m=max_radius_m,
)
places = [
NearbyPlace(
id=place.id,
slug=place.slug,
category=place.category,
location=LatLon(lat=place_lat, lon=place_lon),
address=place.address,
district=place.district,
built_year=place.built_year,
architect_builder=place.architect_builder,
architectural_style=place.architectural_style,
content=ContentOut(
language=content.language, length=content.length, title=content.title, body=content.body
),
distance_m=round(distance_m, 1),
)
for place, content, distance_m, place_lon, place_lat in result.rows
]
return NearbyResponse(search_radius_m=result.search_radius_m, count=len(places), places=places)