"""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)