from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, or_ from typing import Optional, List import json import urllib.request import urllib.parse import logging from app.database import get_db from app.models.location import Location from app.schemas.location import LocationCreate, LocationUpdate, LocationResponse, LocationSearchResult from app.routers.auth import get_current_session from app.models.settings import Settings logger = logging.getLogger(__name__) router = APIRouter() @router.get("/search", response_model=List[LocationSearchResult]) async def search_locations( q: str = Query(..., min_length=1), db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session), ): """Search locations from local DB and Nominatim OSM.""" results: List[LocationSearchResult] = [] # Local DB search local_query = ( select(Location) .where( or_( Location.name.ilike(f"%{q}%"), Location.address.ilike(f"%{q}%"), ) ) .limit(5) ) local_result = await db.execute(local_query) local_locations = local_result.scalars().all() for loc in local_locations: results.append( LocationSearchResult( source="local", location_id=loc.id, name=loc.name, address=loc.address, ) ) # Nominatim proxy search try: encoded_q = urllib.parse.quote(q) url = f"https://nominatim.openstreetmap.org/search?q={encoded_q}&format=json&limit=5" req = urllib.request.Request( url, headers={"User-Agent": "UMBRA-LifeManager/1.0"}, ) with urllib.request.urlopen(req, timeout=5) as resp: osm_data = json.loads(resp.read().decode()) for item in osm_data: display_name = item.get("display_name", "") name_parts = display_name.split(",", 1) name = name_parts[0].strip() address = name_parts[1].strip() if len(name_parts) > 1 else display_name results.append( LocationSearchResult( source="nominatim", name=name, address=address, ) ) except Exception as e: logger.warning(f"Nominatim search failed: {e}") return results @router.get("/", response_model=List[LocationResponse]) async def get_locations( category: Optional[str] = Query(None), db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get all locations with optional category filter.""" query = select(Location) if category: query = query.where(Location.category == category) query = query.order_by(Location.name.asc()) result = await db.execute(query) locations = result.scalars().all() return locations @router.post("/", response_model=LocationResponse, status_code=201) async def create_location( location: LocationCreate, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Create a new location.""" new_location = Location(**location.model_dump()) db.add(new_location) await db.commit() await db.refresh(new_location) return new_location @router.get("/{location_id}", response_model=LocationResponse) async def get_location( location_id: int, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get a specific location by ID.""" result = await db.execute(select(Location).where(Location.id == location_id)) location = result.scalar_one_or_none() if not location: raise HTTPException(status_code=404, detail="Location not found") return location @router.put("/{location_id}", response_model=LocationResponse) async def update_location( location_id: int, location_update: LocationUpdate, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Update a location.""" result = await db.execute(select(Location).where(Location.id == location_id)) location = result.scalar_one_or_none() if not location: raise HTTPException(status_code=404, detail="Location not found") update_data = location_update.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(location, key, value) await db.commit() await db.refresh(location) return location @router.delete("/{location_id}", status_code=204) async def delete_location( location_id: int, db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Delete a location.""" result = await db.execute(select(Location).where(Location.id == location_id)) location = result.scalar_one_or_none() if not location: raise HTTPException(status_code=404, detail="Location not found") await db.delete(location) await db.commit() return None