- FastAPI backend: PostGIS estimator + 3 scrapers (Avito/Cian/Yandex)
- Next.js 15 frontend: tradein.html mockup design, basePath=/trade-in
- WeasyPrint PDF (Брусника-style 4-page report)
- Address autocomplete с typo-tolerance + 6 EKB presets
- Изолированный docker stack gendesign-tradein (отдельная postgres БД)
- Caddy inline routes: gendsgn.ru/trade-in/* и /trade-in/api/v1/*
- Forgejo Actions: .forgejo/workflows/deploy-tradein.yml (shell-based GHCR login)
- Триггер только по paths: tradein-mvp/** (не пересекается с deploy.yml)
- Образы: ghcr.io/lekss361/gendesign-tradein-{backend,frontend}:latest
Первый запуск на сервере (вручную, один раз):
- создать /opt/gendesign/tradein-mvp/.env.runtime (postgres pwd, contact email)
- docker network create gendesign_shared (если нет)
- docker compose -p gendesign-tradein up -d
- docker compose -p gendesign exec caddy caddy reload
70 lines
2.1 KiB
Python
70 lines
2.1 KiB
Python
"""Geocode endpoints — debug + frontend Suggest proxy."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.db import get_db
|
|
from app.services.geocoder import GeocodeResult, geocode, suggest
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/lookup", response_model=GeocodeResult)
|
|
async def lookup(
|
|
address: Annotated[str, Query(min_length=3, max_length=500)],
|
|
db: Annotated[Session, Depends(get_db)],
|
|
) -> GeocodeResult:
|
|
"""Геокодинг адреса → lat/lon.
|
|
|
|
Примеры:
|
|
/api/v1/geocode/lookup?address=ул.+Малышева+30+Екатеринбург
|
|
/api/v1/geocode/lookup?address=Куйбышева+50+Екатеринбург
|
|
"""
|
|
result = await geocode(address, db)
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail=f"Address not found: {address}")
|
|
return result
|
|
|
|
|
|
class SuggestItem(BaseModel):
|
|
label: str
|
|
full_address: str
|
|
lat: float
|
|
lon: float
|
|
kind: str
|
|
|
|
|
|
class SuggestResponse(BaseModel):
|
|
items: list[SuggestItem]
|
|
|
|
|
|
@router.get("/suggest", response_model=SuggestResponse)
|
|
async def suggest_addresses(
|
|
q: Annotated[str, Query(min_length=2, max_length=200, description="Запрос для автокомплита")],
|
|
limit: Annotated[int, Query(ge=1, le=15)] = 8,
|
|
) -> SuggestResponse:
|
|
"""Автокомплит адресов в пределах ЕКБ.
|
|
|
|
Используется в EstimateForm для подсказок пока пользователь печатает.
|
|
Bounded viewbox = ЕКБ (lon 60.40-60.85, lat 56.65-56.95).
|
|
|
|
Пример:
|
|
/api/v1/geocode/suggest?q=Малышева
|
|
/api/v1/geocode/suggest?q=Цвиллинга # → пусто, такой улицы в ЕКБ нет
|
|
"""
|
|
items = await suggest(q, limit=limit)
|
|
return SuggestResponse(items=[
|
|
SuggestItem(
|
|
label=s.label,
|
|
full_address=s.full_address,
|
|
lat=s.lat, lon=s.lon, kind=s.kind,
|
|
) for s in items
|
|
])
|