""" Linux BenchTools - Locations API Endpoints """ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from sqlalchemy.orm import Session from typing import List, Optional import os import shutil from app.db.session import get_peripherals_db from app.services.peripheral_service import LocationService from app.schemas.peripheral import ( LocationCreate, LocationUpdate, LocationSchema, LocationTreeNode ) from app.models.location import Location from app.utils.image_processor import ImageProcessor from app.utils.qr_generator import QRCodeGenerator from app.core.config import settings router = APIRouter() # ======================================== # LOCATION CRUD # ======================================== @router.post("/", response_model=LocationSchema, status_code=201) def create_location( location: LocationCreate, db: Session = Depends(get_peripherals_db) ): """Create a new location""" # Check parent exists if specified if location.parent_id: parent = db.query(Location).filter(Location.id == location.parent_id).first() if not parent: raise HTTPException(status_code=404, detail="Parent location not found") # Check for duplicate name existing = db.query(Location).filter(Location.nom == location.nom).first() if existing: raise HTTPException(status_code=400, detail="Location with this name already exists") db_location = Location(**location.model_dump()) db.add(db_location) db.commit() db.refresh(db_location) return db_location @router.get("/", response_model=List[LocationSchema]) def list_locations( parent_id: Optional[int] = None, db: Session = Depends(get_peripherals_db) ): """List all locations (optionally filtered by parent)""" query = db.query(Location) if parent_id is not None: query = query.filter(Location.parent_id == parent_id) return query.order_by(Location.ordre_affichage, Location.nom).all() @router.get("/tree", response_model=List[dict]) def get_location_tree(db: Session = Depends(get_peripherals_db)): """Get hierarchical location tree""" return LocationService.get_location_tree(db) @router.get("/{location_id}", response_model=LocationSchema) def get_location( location_id: int, db: Session = Depends(get_peripherals_db) ): """Get a location by ID""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") return location @router.get("/{location_id}/path", response_model=List[LocationSchema]) def get_location_path( location_id: int, db: Session = Depends(get_peripherals_db) ): """Get full path from root to location""" path = LocationService.get_location_path(db, location_id) if not path: raise HTTPException(status_code=404, detail="Location not found") return path @router.put("/{location_id}", response_model=LocationSchema) def update_location( location_id: int, location_data: LocationUpdate, db: Session = Depends(get_peripherals_db) ): """Update a location""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") # Check parent exists if being changed update_dict = location_data.model_dump(exclude_unset=True) if "parent_id" in update_dict and update_dict["parent_id"]: parent = db.query(Location).filter(Location.id == update_dict["parent_id"]).first() if not parent: raise HTTPException(status_code=404, detail="Parent location not found") # Prevent circular reference if update_dict["parent_id"] == location_id: raise HTTPException(status_code=400, detail="Location cannot be its own parent") # Check for duplicate name if name is being changed if "nom" in update_dict and update_dict["nom"] != location.nom: existing = db.query(Location).filter(Location.nom == update_dict["nom"]).first() if existing: raise HTTPException(status_code=400, detail="Location with this name already exists") # Update fields for key, value in update_dict.items(): setattr(location, key, value) db.commit() db.refresh(location) return location @router.delete("/{location_id}", status_code=204) def delete_location( location_id: int, db: Session = Depends(get_peripherals_db) ): """Delete a location""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") # Check if location has children children = db.query(Location).filter(Location.parent_id == location_id).count() if children > 0: raise HTTPException(status_code=400, detail="Cannot delete location with children") # Check if location has peripherals count = LocationService.count_peripherals_in_location(db, location_id) if count > 0: raise HTTPException(status_code=400, detail="Cannot delete location with peripherals") # Delete image and QR code files if they exist if location.image_path and os.path.exists(location.image_path): os.remove(location.image_path) if location.qr_code_path and os.path.exists(location.qr_code_path): os.remove(location.qr_code_path) db.delete(location) db.commit() @router.get("/{location_id}/count") def count_peripherals( location_id: int, recursive: bool = False, db: Session = Depends(get_peripherals_db) ): """Count peripherals in a location""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") count = LocationService.count_peripherals_in_location(db, location_id, recursive) return {"location_id": location_id, "count": count, "recursive": recursive} # ======================================== # LOCATION IMAGES # ======================================== @router.post("/{location_id}/image", response_model=LocationSchema) async def upload_location_image( location_id: int, file: UploadFile = File(...), db: Session = Depends(get_peripherals_db) ): """Upload an image for a location""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") # Validate image temp_path = f"/tmp/{file.filename}" with open(temp_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) if not ImageProcessor.is_valid_image(temp_path): os.remove(temp_path) raise HTTPException(status_code=400, detail="Invalid image file") # Create upload directory upload_dir = os.path.join(settings.PERIPHERALS_UPLOAD_DIR, "locations", "images") os.makedirs(upload_dir, exist_ok=True) try: # Process image processed_path, _ = ImageProcessor.process_image( temp_path, upload_dir, max_width=800, max_height=600 ) # Delete old image if exists if location.image_path and os.path.exists(location.image_path): os.remove(location.image_path) # Update location location.image_path = processed_path db.commit() db.refresh(location) return location finally: if os.path.exists(temp_path): os.remove(temp_path) @router.delete("/{location_id}/image", status_code=204) def delete_location_image( location_id: int, db: Session = Depends(get_peripherals_db) ): """Delete location image""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") if location.image_path and os.path.exists(location.image_path): os.remove(location.image_path) location.image_path = None db.commit() # ======================================== # LOCATION QR CODES # ======================================== @router.post("/{location_id}/qr-code", response_model=LocationSchema) def generate_qr_code( location_id: int, base_url: str, db: Session = Depends(get_peripherals_db) ): """Generate QR code for a location""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") # Create QR code directory qr_dir = os.path.join(settings.PERIPHERALS_UPLOAD_DIR, "locations", "qrcodes") os.makedirs(qr_dir, exist_ok=True) # Generate QR code qr_path = QRCodeGenerator.generate_location_qr( location_id=location.id, location_name=location.nom, base_url=base_url, output_dir=qr_dir ) # Delete old QR code if exists if location.qr_code_path and os.path.exists(location.qr_code_path): os.remove(location.qr_code_path) # Update location location.qr_code_path = qr_path db.commit() db.refresh(location) return location @router.delete("/{location_id}/qr-code", status_code=204) def delete_qr_code( location_id: int, db: Session = Depends(get_peripherals_db) ): """Delete location QR code""" location = db.query(Location).filter(Location.id == location_id).first() if not location: raise HTTPException(status_code=404, detail="Location not found") if location.qr_code_path and os.path.exists(location.qr_code_path): os.remove(location.qr_code_path) location.qr_code_path = None db.commit()