Buck_Tracker / api /camera.py
codewithRiz's picture
camera edit fixed
4f1c854
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field, validator
from typing import List, Optional
from .utils import (
save_cameras,
load_cameras,
user_exists,
_bucket_key,
_list_prefix,
_read_bucket_json,
_write_bucket_json,
BUCKET_ID,
)
from huggingface_hub import batch_bucket_files, download_bucket_files
import tempfile
import os
print(" CAMERA API LOADED ")
router = APIRouter(prefix="/camera", tags=["Camera"])
# ================= MODELS =================
class CameraData(BaseModel):
user_id: str = Field(..., min_length=1)
camera_name: str = Field(..., min_length=1)
camera_loc: Optional[List[float]] = None
@validator("camera_loc")
def validate_loc(cls, loc):
if loc is None:
return loc
if len(loc) != 2:
raise ValueError("camera_loc must be [lat, lon]")
lat, lon = loc
if not (-90 <= lat <= 90):
raise ValueError("Latitude must be between -90 and 90")
if not (-180 <= lon <= 180):
raise ValueError("Longitude must be between -180 and 180")
return loc
class EditCameraData(BaseModel):
user_id: str
old_camera_name: str
new_camera_name: str
new_camera_loc: Optional[List[float]] = None # ← optional, won't error if missing
@validator("new_camera_loc")
def validate_loc(cls, loc):
if loc is None:
return loc
if len(loc) != 2:
raise ValueError("new_camera_loc must be [lat, lon]")
lat, lon = loc
if not (-90 <= lat <= 90):
raise ValueError("Latitude must be between -90 and 90")
if not (-180 <= lon <= 180):
raise ValueError("Longitude must be between -180 and 180")
return loc
# ================= ROUTES =================
@router.get("/")
def home():
return {
"message": "Camera API is Running",
"endpoints": [
"/camera/add_camera",
"/camera/edit_camera",
"/camera/delete_camera",
"/camera/get_cameras?user_id=<id>"
]
}
# ---------- GET CAMERAS ----------
@router.get("/get_cameras")
def get_cameras(user_id: str = Query(...)):
if not user_exists(user_id):
raise HTTPException(status_code=404, detail="User not found")
cameras = load_cameras(user_id)
return {"success": True, "user_id": user_id, "cameras": cameras, "count": len(cameras)}
# ---------- ADD CAMERA ----------
@router.post("/add_camera")
def add_camera(data: CameraData):
cameras = load_cameras(data.user_id)
if len(cameras) >= 2:
raise HTTPException(status_code=400, detail="Only 2 cameras allowed")
for cam in cameras:
if cam["camera_name"].lower() == data.camera_name.lower():
raise HTTPException(status_code=400, detail="Camera already exists")
cameras.append({"camera_name": data.camera_name, "camera_loc": data.camera_loc})
save_cameras(data.user_id, cameras)
return {"success": True, "camera": data.camera_name}
# ---------- EDIT CAMERA ----------
@router.put("/edit_camera")
def edit_camera(data: EditCameraData):
if not user_exists(data.user_id):
raise HTTPException(status_code=404, detail="User not found")
cameras = load_cameras(data.user_id)
# Only block duplicate name if name is actually changing
name_changing = data.new_camera_name.lower() != data.old_camera_name.lower()
if name_changing:
if any(cam["camera_name"].lower() == data.new_camera_name.lower() for cam in cameras):
raise HTTPException(
status_code=400,
detail=f"Camera name '{data.new_camera_name}' already exists"
)
camera_found = False
for cam in cameras:
if cam["camera_name"].lower() == data.old_camera_name.lower():
old_name = cam["camera_name"]
# Apply updates
cam["camera_name"] = data.new_camera_name
if data.new_camera_loc is not None: # only update if provided
cam["camera_loc"] = data.new_camera_loc
camera_found = True
# ── Rename bucket files only if name changed ──────────
if name_changing:
old_prefix = _bucket_key(data.user_id, old_name)
new_prefix = _bucket_key(data.user_id, data.new_camera_name)
old_files = _list_prefix(old_prefix)
for item in old_files:
old_key = item.path
new_key = old_key.replace(old_prefix, new_prefix, 1)
# Rename detection JSON filename inside the key
if f"{old_name}_detections.json" in new_key:
new_key = new_key.replace(
f"{old_name}_detections.json",
f"{data.new_camera_name}_detections.json"
)
try:
if old_key.endswith(".json"):
# JSON: read and re-write
content = _read_bucket_json(old_key)
if content is not None:
_write_bucket_json(new_key, content)
else:
# Binary (images): download β†’ re-upload
with tempfile.NamedTemporaryFile(delete=False) as tf:
tmp_path = tf.name
download_bucket_files(
BUCKET_ID,
files=[(old_key, tmp_path)],
)
with open(tmp_path, "rb") as f:
raw = f.read()
os.unlink(tmp_path)
batch_bucket_files(
BUCKET_ID,
add=[(raw, new_key)],
)
# Delete old key after successful copy
batch_bucket_files(
BUCKET_ID,
delete=[old_key],
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to move bucket file '{old_key}': {str(e)}"
)
save_cameras(data.user_id, cameras)
return {"success": True, "updated": cam}
if not camera_found:
raise HTTPException(status_code=404, detail="Camera not found")
# ---------- DELETE CAMERA ----------
@router.delete("/delete_camera")
def delete_camera(user_id: str = Query(...), camera_name: str = Query(...)):
if not user_exists(user_id):
raise HTTPException(status_code=404, detail="User not found")
cameras = load_cameras(user_id)
new_list = [c for c in cameras if c["camera_name"].lower() != camera_name.lower()]
if len(new_list) == len(cameras):
raise HTTPException(status_code=404, detail="Camera not found")
# Delete all bucket files under this camera prefix
cam_prefix = _bucket_key(user_id, camera_name)
cam_files = _list_prefix(cam_prefix)
if cam_files:
keys_to_delete = [item.path for item in cam_files]
try:
batch_bucket_files(
BUCKET_ID,
delete=keys_to_delete,
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to delete camera files from bucket: {str(e)}"
)
save_cameras(user_id, new_list)
return {"success": True, "deleted": camera_name}