from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from fastapi.responses import Response from sqlalchemy.orm import Session from backend.app.api import deps from backend.app.core.database import get_db from backend.app.models import sql_models from backend.app.schemas import schemas from backend.app.services.video_core import video_manager import openpyxl import io router = APIRouter() @router.get("", response_model=List[schemas.Camera]) def read_cameras( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: cameras = db.query(sql_models.Camera).offset(skip).limit(limit).all() # Update status from video_manager for cam in cameras: stream = video_manager.streams.get(cam.id) if stream: cam.status = stream.status else: cam.status = 0 return cameras @router.get("/export_template") def export_template( db: Session = Depends(get_db), current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: """ Export camera configuration template (including current data) """ wb = openpyxl.Workbook() ws = wb.active ws.title = "Cameras" # Headers headers = ["Name", "Stream URL"] ws.append(headers) # Data cameras = db.query(sql_models.Camera).all() for cam in cameras: ws.append([cam.name, cam.stream_url]) # Adjust column width ws.column_dimensions['A'].width = 30 ws.column_dimensions['B'].width = 60 # Save to buffer buffer = io.BytesIO() wb.save(buffer) buffer.seek(0) return Response( content=buffer.getvalue(), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": "attachment; filename=cameras_template.xlsx"} ) @router.post("/import_template") async def import_template( file: UploadFile = File(...), db: Session = Depends(get_db), current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: """ Import cameras from template """ if not file.filename.endswith(('.xlsx', '.xls')): raise HTTPException(status_code=400, detail="Invalid file format. Please upload an Excel file.") try: contents = await file.read() wb = openpyxl.load_workbook(io.BytesIO(contents)) ws = wb.active # Read headers rows = list(ws.rows) if not rows: return {"message": "Empty file"} header_row = rows[0] headers = [cell.value for cell in header_row] # Validate headers if "Name" not in headers or "Stream URL" not in headers: raise HTTPException(status_code=400, detail="Invalid template format. Required columns: Name, Stream URL") name_idx = headers.index("Name") url_idx = headers.index("Stream URL") added_count = 0 updated_count = 0 # Process data for row in rows[1:]: name = row[name_idx].value url = row[url_idx].value if not name or not url: continue # Check if camera exists by URL existing_cam = db.query(sql_models.Camera).filter(sql_models.Camera.stream_url == url).first() if existing_cam: # Update name if changed if existing_cam.name != name: existing_cam.name = name updated_count += 1 else: # Create new camera new_cam = sql_models.Camera(name=name, stream_url=url) db.add(new_cam) db.flush() # Get ID video_manager.add_stream(new_cam.id, new_cam.stream_url) added_count += 1 db.commit() return { "message": "Import successful", "added": added_count, "updated": updated_count } except Exception as e: raise HTTPException(status_code=400, detail=f"Import failed: {str(e)}") @router.post("", response_model=schemas.Camera) def create_camera( *, db: Session = Depends(get_db), camera_in: schemas.CameraCreate, current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: camera = sql_models.Camera(name=camera_in.name, stream_url=camera_in.stream_url) db.add(camera) db.commit() db.refresh(camera) # Start stream video_manager.add_stream(camera.id, camera.stream_url) return camera @router.put("/{id}", response_model=schemas.Camera) def update_camera( *, db: Session = Depends(get_db), id: int, camera_in: schemas.CameraUpdate, current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first() if not camera: raise HTTPException(status_code=404, detail="Camera not found") camera.name = camera_in.name camera.stream_url = camera_in.stream_url db.commit() db.refresh(camera) # Restart stream with new URL video_manager.add_stream(camera.id, camera.stream_url) return camera @router.delete("/{id}", response_model=schemas.Camera) def delete_camera( *, db: Session = Depends(get_db), id: int, current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: camera = db.query(sql_models.Camera).filter(sql_models.Camera.id == id).first() if not camera: raise HTTPException(status_code=404, detail="Camera not found") # Stop stream video_manager.stop_stream(id) db.delete(camera) db.commit() return camera @router.post("/test") def test_camera( *, camera_in: schemas.CameraTest, current_user: sql_models.User = Depends(deps.get_current_user) ) -> Any: snapshot = video_manager.test_camera_connection(camera_in.stream_url) if not snapshot: raise HTTPException(status_code=400, detail="Unable to connect to camera stream") return {"status": "ok", "image": snapshot}