from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Dict, Any, Optional import uuid import datetime from backend.core.geo_engine import get_geo_engine from backend.core.session_store import get_session_store router = APIRouter() # Must match executor.py's DEFAULT_SESSION_ID DEFAULT_SESSION_ID = "default-session" class UserLayerRequest(BaseModel): geojson: Dict[str, Any] name: str = "User Draw" description: Optional[str] = None class UserLayerResponse(BaseModel): id: str name: str # The table name in DuckDB display_name: str # Registered with and without the trailing slash. Starlette only redirects # "/layers/user" -> "/layers/user/" when nothing else matches, and the # catch-all that serves the frontend matches everything — so in the deployed # build the redirect never happened and a POST came back 405. @router.post("", response_model=UserLayerResponse) @router.post("/", response_model=UserLayerResponse, include_in_schema=False) async def register_user_layer(request: UserLayerRequest): """ Registers a new user-drawn layer. Saves GeoJSON to backend and makes it available for SQL querying. """ try: layer_id = str(uuid.uuid4()) geo_engine = get_geo_engine() session_store = get_session_store() # Use singleton # Register in DuckDB table_name = geo_engine.register_layer(layer_id, request.geojson) # Register in Session Store layer_metadata = { "id": layer_id, "name": request.name, "table_name": table_name, "type": "user_draw", "timestamp": datetime.datetime.now().isoformat(), "description": request.description } # Use the same session ID as executor.py session_store.add_layer(DEFAULT_SESSION_ID, layer_metadata) return UserLayerResponse( id=layer_id, name=table_name, display_name=request.name ) except Exception as e: import traceback traceback.print_exc() raise HTTPException(status_code=500, detail=str(e)) @router.delete("/{layer_id}") async def delete_user_layer(layer_id: str): """ Deletes a user-drawn layer from DuckDB and session store. Frees backend resources and prevents agent from querying the layer. """ try: geo_engine = get_geo_engine() session_store = get_session_store() # Remove from DuckDB dropped = geo_engine.drop_layer(layer_id) # Remove from session store removed = session_store.remove_layer(DEFAULT_SESSION_ID, layer_id) if not dropped and not removed: raise HTTPException(status_code=404, detail=f"Layer {layer_id} not found") return {"status": "deleted", "layer_id": layer_id} except HTTPException: raise except Exception as e: import traceback traceback.print_exc() raise HTTPException(status_code=500, detail=str(e))