""" Image processing utilities for photo uploads, cropping, and storage. Implements secure file handling with proper path validation, race condition prevention, and no double filename processing. """ from pathlib import Path from typing import Optional, Dict, Any import hashlib import os # Configuration IMAGES_DIR = Path("images") IMAGES_DIR.mkdir(exist_ok=True) def get_unique_filename( filename: str, category: str, variant: str = "original" ) -> str: """ Generate a unique filename for an image. Args: filename: Base filename (without extension) category: Category for organization variant: "original" or "thumbnail" Returns: Unique filename with extension """ # Ensure directory exists cat_dir = IMAGES_DIR / category cat_dir.mkdir(parents=True, exist_ok=True) # Sanitize filename safe_name = "".join(c for c in filename if c.isalnum() or c in ("_", "-", " ")).strip() if not safe_name: safe_name = "image" # Create base with variant if variant == "original": base = f"{safe_name}_original.jpg" elif variant == "thumbnail": base = f"{safe_name}_thumb.jpg" else: base = f"{safe_name}.jpg" # Check for collisions target = cat_dir / base if not target.exists(): return str(target.relative_to(IMAGES_DIR.parent)) # Add hash suffix if collision name_hash = hashlib.md5(str(os.urandom(16)).encode()).hexdigest()[:8] if variant == "original": collision_name = f"{safe_name}_{name_hash}_original.jpg" elif variant == "thumbnail": collision_name = f"{safe_name}_{name_hash}_thumb.jpg" else: collision_name = f"{safe_name}_{name_hash}.jpg" target = cat_dir / collision_name return str(target.relative_to(IMAGES_DIR.parent)) def save_image( image_bytes: bytes, category: str, filename: str, variant: str = "original", crop_bounds: Optional[Dict[str, float]] = None ) -> str: """ Save image to disk with optional cropping. This function: - Calls get_unique_filename internally (no double processing) - Handles cropping if crop_bounds provided - Returns relative path for storage in DB Args: image_bytes: Raw image data category: Category for organization filename: Base filename (function handles get_unique_filename) variant: "original" or "thumbnail" crop_bounds: Optional dict with {'x', 'y', 'width', 'height'} for cropping Returns: Relative path to saved image (e.g., "category/filename_original.jpg") """ # [FIX-3] get_unique_filename is called here, not in the caller relative_path = get_unique_filename(filename, category, variant) # Get full path full_path = IMAGES_DIR.parent / relative_path full_path.parent.mkdir(parents=True, exist_ok=True) # TODO: Implement actual image processing with PIL/OpenCV # For now, save raw bytes # In production, this would: # - Load image with PIL # - Apply cropping if crop_bounds provided # - Resize for thumbnails # - Apply compression with open(full_path, "wb") as f: f.write(image_bytes) return relative_path