Files
service-finder/backend/app/api/v1/endpoints/locations.py
2026-07-02 11:52:22 +00:00

94 lines
3.1 KiB
Python

"""
🌍 Location Autocomplete API Endpoint.
Provides autocomplete search for postal codes and cities from the
system.geo_postal_codes table. Used by the frontend SmartLocationInput
component to let users quickly find and select a zip/city pair.
Schema: system.geo_postal_codes
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db
from app.models.identity.address import GeoPostalCode
logger = logging.getLogger(__name__)
router = APIRouter()
class LocationAutocompleteItem(BaseModel):
"""Egy találat a település autocomplete keresőben."""
id: int
zip_code: str
city: str
country_code: str = "HU"
model_config = {"from_attributes": True}
@router.get("/locations/autocomplete", response_model=List[LocationAutocompleteItem])
async def autocomplete_locations(
q: str = Query(..., min_length=1, max_length=100, description="Keresőszó (irányítószám vagy városrészlet)"),
country_code: str = Query("HU", max_length=5, description="Országkód szűrő (alapértelmezett: HU)"),
limit: int = Query(15, ge=1, le=50, description="Max találatok száma"),
db: AsyncSession = Depends(get_db),
):
"""
GET /api/v1/locations/autocomplete?q=budapest
Autocomplete keresés a system.geo_postal_codes táblában.
A keresés az irányítószámra (zip_code) és a város névre (city) is
történik ILIKE operátorral.
Példák:
/api/v1/locations/autocomplete?q=1011 -> 1011 Budapest
/api/v1/locations/autocomplete?q=budapest -> 1011 Budapest, 1012 Budapest, ...
/api/v1/locations/autocomplete?q=101 -> 1011 Budapest, 1012 Budapest, ...
/api/v1/locations/autocomplete?q=bud&country_code=AT -> 5020 Salzburg, ...
"""
if not q.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="A keresőszó (q) nem lehet üres.",
)
try:
stmt = (
select(GeoPostalCode)
.where(
GeoPostalCode.country_code == country_code,
or_(
GeoPostalCode.zip_code.ilike(f"%{q.strip()}%"),
GeoPostalCode.city.ilike(f"%{q.strip()}%"),
),
)
.order_by(GeoPostalCode.zip_code, GeoPostalCode.city)
.limit(limit)
)
result = await db.execute(stmt)
items = result.scalars().all()
return [
LocationAutocompleteItem(
id=item.id,
zip_code=item.zip_code,
city=item.city,
country_code=item.country_code,
)
for item in items
]
except Exception as e:
logger.error(f"Hiba a location autocomplete során: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Hiba történt a település keresés során.",
)