wrongmove/cli/pois.py

249 lines
9.2 KiB
Python

"""Debug CLI — POI commands."""
import click
import httpx
from cli._context import CliContext, get_http_headers, output, error_output, resolve_user_id
from database import engine
from models.listing import ListingType
from repositories.poi_repository import POIRepository
from services import poi_service, task_service
@click.group("pois")
def pois_group() -> None:
"""Point of Interest management commands."""
pass
@pois_group.command("list")
@click.pass_context
def list_pois(ctx: click.Context) -> None:
"""List all POIs for the user."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
try:
if cli_ctx.use_http:
resp = httpx.get(
f"{cli_ctx.api_base_url}/api/poi",
headers=get_http_headers(cli_ctx.user_email),
)
resp.raise_for_status()
data = resp.json()
else:
user_id = resolve_user_id(cli_ctx.user_email)
repo = POIRepository(engine)
pois = poi_service.get_user_pois(repo, user_id)
data = [
{
"id": p.id,
"name": p.name,
"address": p.address,
"latitude": p.latitude,
"longitude": p.longitude,
"created_at": str(p.created_at),
}
for p in pois
]
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)
@pois_group.command("create")
@click.option("--name", required=True, help="POI name")
@click.option("--address", required=True, help="Address")
@click.option("--lat", required=True, type=float, help="Latitude")
@click.option("--lon", required=True, type=float, help="Longitude")
@click.pass_context
def create_poi(ctx: click.Context, name: str, address: str, lat: float, lon: float) -> None:
"""Create a new POI."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
try:
if cli_ctx.use_http:
resp = httpx.post(
f"{cli_ctx.api_base_url}/api/poi",
headers=get_http_headers(cli_ctx.user_email),
json={"name": name, "address": address, "latitude": lat, "longitude": lon},
)
resp.raise_for_status()
data = resp.json()
else:
user_id = resolve_user_id(cli_ctx.user_email)
repo = POIRepository(engine)
result = poi_service.create_poi(repo, user_id, name, address, lat, lon)
data = {
"id": result.poi.id,
"name": result.poi.name,
"address": result.poi.address,
"latitude": result.poi.latitude,
"longitude": result.poi.longitude,
}
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)
@pois_group.command("update")
@click.argument("poi_id", type=int)
@click.option("--name", default=None, help="New name")
@click.option("--address", default=None, help="New address")
@click.option("--lat", default=None, type=float, help="New latitude")
@click.option("--lon", default=None, type=float, help="New longitude")
@click.pass_context
def update_poi(ctx: click.Context, poi_id: int, name: str | None, address: str | None, lat: float | None, lon: float | None) -> None:
"""Update an existing POI."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
try:
if cli_ctx.use_http:
body: dict[str, str | float] = {}
if name is not None:
body["name"] = name
if address is not None:
body["address"] = address
if lat is not None:
body["latitude"] = lat
if lon is not None:
body["longitude"] = lon
resp = httpx.put(
f"{cli_ctx.api_base_url}/api/poi/{poi_id}",
headers=get_http_headers(cli_ctx.user_email),
json=body,
)
resp.raise_for_status()
data = resp.json()
else:
user_id = resolve_user_id(cli_ctx.user_email)
repo = POIRepository(engine)
result = poi_service.update_poi(repo, poi_id, user_id, name, address, lat, lon)
if result is None:
error_output("POI not found", cli_ctx.json_output)
return
data = {
"id": result.poi.id,
"name": result.poi.name,
"address": result.poi.address,
"latitude": result.poi.latitude,
"longitude": result.poi.longitude,
}
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)
@pois_group.command("delete")
@click.argument("poi_id", type=int)
@click.pass_context
def delete_poi(ctx: click.Context, poi_id: int) -> None:
"""Delete a POI."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
try:
if cli_ctx.use_http:
resp = httpx.delete(
f"{cli_ctx.api_base_url}/api/poi/{poi_id}",
headers=get_http_headers(cli_ctx.user_email),
)
resp.raise_for_status()
data = resp.json()
else:
user_id = resolve_user_id(cli_ctx.user_email)
repo = POIRepository(engine)
deleted = poi_service.delete_poi(repo, poi_id, user_id)
if not deleted:
error_output("POI not found", cli_ctx.json_output)
return
data = {"success": True}
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)
@pois_group.command("calculate")
@click.argument("poi_id", type=int)
@click.option("--travel-modes", required=True, help="Comma-separated: WALK,BICYCLE,TRANSIT")
@click.option("--type", "-t", "listing_type", required=True, type=click.Choice(["RENT", "BUY"]))
@click.pass_context
def calculate_distances(ctx: click.Context, poi_id: int, travel_modes: str, listing_type: str) -> None:
"""Trigger distance calculation for a POI."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
modes = [m.strip().upper() for m in travel_modes.split(",")]
lt = ListingType[listing_type]
try:
if cli_ctx.use_http:
resp = httpx.post(
f"{cli_ctx.api_base_url}/api/poi/{poi_id}/calculate",
headers=get_http_headers(cli_ctx.user_email),
json={"travel_modes": modes, "listing_type": listing_type},
)
resp.raise_for_status()
data = resp.json()
else:
result = poi_service.trigger_calculation(
poi_id=poi_id,
travel_modes=modes,
listing_type=lt,
user_email=cli_ctx.user_email,
)
if result.task_id:
task_service.add_task_for_user(cli_ctx.user_email, result.task_id)
data = {"task_id": result.task_id or "", "message": result.message}
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)
@pois_group.command("distances")
@click.argument("listing_id", type=int)
@click.option("--type", "-t", "listing_type", default="RENT", type=click.Choice(["RENT", "BUY"]))
@click.pass_context
def get_distances(ctx: click.Context, listing_id: int, listing_type: str) -> None:
"""Get POI distances for a specific listing."""
cli_ctx: CliContext = ctx.obj["cli_ctx"]
lt = ListingType[listing_type]
try:
if cli_ctx.use_http:
resp = httpx.get(
f"{cli_ctx.api_base_url}/api/poi/distances",
headers=get_http_headers(cli_ctx.user_email),
params={"listing_id": listing_id, "listing_type": listing_type},
)
resp.raise_for_status()
data = resp.json()
else:
user_id = resolve_user_id(cli_ctx.user_email)
repo = POIRepository(engine)
distances = poi_service.get_distances_for_listing(repo, listing_id, lt, user_id)
data = [
{
"poi_id": d.poi_id,
"travel_mode": d.travel_mode,
"duration_seconds": d.duration_seconds,
"distance_meters": d.distance_meters,
}
for d in distances
]
output(data, cli_ctx.json_output)
except httpx.HTTPStatusError as e:
error_output(f"HTTP {e.response.status_code}: {e.response.text}", cli_ctx.json_output)
except Exception as e:
error_output(str(e), cli_ctx.json_output)