perch / backend /api /endpoints /user_layers.py
GerardCB's picture
Fix 405 when saving a drawn layer in the deployed build
05d7afb verified
Raw
History Blame Contribute Delete
3.11 kB
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))