"""Fences Router - geofence management API endpoints.""" import math from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import require_write from app.database import get_db from app.schemas import ( APIResponse, DeviceFenceBindRequest, FenceConfigCreate, FenceConfigResponse, FenceConfigUpdate, FenceDeviceDetail, PaginatedList, ) from app.services import fence_service router = APIRouter(prefix="/api/fences", tags=["Fences"]) @router.get("", response_model=APIResponse[PaginatedList[FenceConfigResponse]]) async def list_fences( is_active: bool | None = Query(default=None), search: str | None = Query(default=None), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), db: AsyncSession = Depends(get_db), ): records, total = await fence_service.get_fences(db, page, page_size, is_active, search) return APIResponse( data=PaginatedList( items=[FenceConfigResponse.model_validate(r) for r in records], total=total, page=page, page_size=page_size, total_pages=math.ceil(total / page_size) if total else 0, ) ) @router.get("/all-active", response_model=APIResponse[list[FenceConfigResponse]]) async def get_all_active(db: AsyncSession = Depends(get_db)): fences = await fence_service.get_all_active_fences(db) return APIResponse(data=[FenceConfigResponse.model_validate(f) for f in fences]) @router.get("/{fence_id}", response_model=APIResponse[FenceConfigResponse]) async def get_fence(fence_id: int, db: AsyncSession = Depends(get_db)): fence = await fence_service.get_fence(db, fence_id) if fence is None: raise HTTPException(status_code=404, detail="Fence not found") return APIResponse(data=FenceConfigResponse.model_validate(fence)) @router.post("", response_model=APIResponse[FenceConfigResponse], status_code=201, dependencies=[Depends(require_write)]) async def create_fence(body: FenceConfigCreate, db: AsyncSession = Depends(get_db)): fence = await fence_service.create_fence(db, body) return APIResponse(message="Fence created", data=FenceConfigResponse.model_validate(fence)) @router.put("/{fence_id}", response_model=APIResponse[FenceConfigResponse], dependencies=[Depends(require_write)]) async def update_fence(fence_id: int, body: FenceConfigUpdate, db: AsyncSession = Depends(get_db)): fence = await fence_service.update_fence(db, fence_id, body) if fence is None: raise HTTPException(status_code=404, detail="Fence not found") return APIResponse(message="Fence updated", data=FenceConfigResponse.model_validate(fence)) @router.delete("/{fence_id}", response_model=APIResponse, dependencies=[Depends(require_write)]) async def delete_fence(fence_id: int, db: AsyncSession = Depends(get_db)): if not await fence_service.delete_fence(db, fence_id): raise HTTPException(status_code=404, detail="Fence not found") return APIResponse(message="Fence deleted") # --------------------------------------------------------------------------- # Device-Fence Binding endpoints # --------------------------------------------------------------------------- @router.get( "/{fence_id}/devices", response_model=APIResponse[list[FenceDeviceDetail]], summary="获取围栏绑定的设备列表", ) async def get_fence_devices(fence_id: int, db: AsyncSession = Depends(get_db)): fence = await fence_service.get_fence(db, fence_id) if fence is None: raise HTTPException(status_code=404, detail="Fence not found") items = await fence_service.get_fence_devices(db, fence_id) return APIResponse(data=[FenceDeviceDetail(**item) for item in items]) @router.post( "/{fence_id}/devices", response_model=APIResponse, dependencies=[Depends(require_write)], summary="绑定设备到围栏", ) async def bind_devices(fence_id: int, body: DeviceFenceBindRequest, db: AsyncSession = Depends(get_db)): result = await fence_service.bind_devices_to_fence(db, fence_id, body.device_ids) if result.get("error"): raise HTTPException(status_code=404, detail=result["error"]) return APIResponse( message=f"绑定完成: 新增{result['created']}, 已绑定{result['already_bound']}, 未找到{result['not_found']}", data=result, ) @router.delete( "/{fence_id}/devices", response_model=APIResponse, dependencies=[Depends(require_write)], summary="解绑设备与围栏", ) async def unbind_devices(fence_id: int, body: DeviceFenceBindRequest, db: AsyncSession = Depends(get_db)): count = await fence_service.unbind_devices_from_fence(db, fence_id, body.device_ids) return APIResponse(message=f"已解绑 {count} 个设备")