checking stuff

This commit is contained in:
2026-04-06 23:53:39 -06:00
parent f812d4a664
commit 6083ab34ca

View File

@@ -0,0 +1,154 @@
"""File storage utilities for report images"""
import os
import logging
from pathlib import Path
from fastapi import UploadFile, HTTPException, status
from PIL import Image
from io import BytesIO
from core.config import ConfSettings
logger = logging.getLogger(__name__)
class ImageStorageManager:
"""Maneja almacenamiento, compresión y eliminación de imágenes de reportes"""
def __init__(self):
self.storage_path = Path(ConfSettings.storage_base_path) / ConfSettings.images_dir
self.max_size_bytes = ConfSettings.images_max_size_mb * 1024 * 1024
self.allowed_types = ConfSettings.images_allowed_types
self.compression_quality = ConfSettings.images_compression_quality
# Crear directorio si no existe
self.storage_path.mkdir(parents=True, exist_ok=True)
logger.info(f"ImageStorageManager initialized with path: {self.storage_path}")
def validate_and_save_image(self, file: UploadFile, report_id: str) -> str:
"""
Valida y guarda una imagen, comprimiendo a WebP.
Args:
file: Archivo subido (UploadFile)
report_id: ID del reporte para nombrado del archivo
Returns:
Nombre del archivo guardado (sin ruta)
Raises:
HTTPException: Si hay error en validación o guardado
"""
try:
# Validar tipo MIME
if file.content_type not in self.allowed_types:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Tipo de archivo no permitido. Permitidos: {', '.join(self.allowed_types)}"
)
# Leer contenido
content = file.file.read()
# Validar tamaño
if len(content) > self.max_size_bytes:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"Archivo demasiado grande. Máximo: {ConfSettings.images_max_size_mb}MB"
)
# Abrir imagen con Pillow
try:
image = Image.open(BytesIO(content))
image.verify() # Verificar que sea una imagen válida
# Reabrir después de verify() que la cierra
image = Image.open(BytesIO(content))
# Convertir a RGB si tiene canal alpha (RGBA)
if image.mode in ('RGBA', 'LA', 'P'):
rgb_image = Image.new('RGB', image.size, (255, 255, 255))
rgb_image.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
image = rgb_image
except Exception as e:
logger.error(f"Error validating image: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Archivo no es una imagen válida"
)
# Guardar como WebP comprimido
filename = f"{report_id}.webp"
filepath = self.storage_path / filename
try:
image.save(
filepath,
"WEBP",
quality=self.compression_quality,
method=6
)
logger.info(f"Image saved: {filename} ({len(open(filepath, 'rb').read())} bytes)")
return filename
except Exception as e:
logger.error(f"Error saving image to disk: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error al guardar la imagen"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Unexpected error processing image: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error procesando la imagen"
)
def get_image_path(self, filename: str) -> Path:
"""Obtiene la ruta completa de una imagen"""
return self.storage_path / filename
def image_exists(self, filename: str) -> bool:
"""Verifica si una imagen existe"""
if not filename:
return False
filepath = self.get_image_path(filename)
return filepath.exists()
def delete_image(self, filename: str) -> bool:
"""
Elimina una imagen del almacenamiento.
Args:
filename: Nombre del archivo a eliminar
Returns:
True si se eliminó, False si no existía
"""
if not filename:
return False
filepath = self.get_image_path(filename)
try:
if filepath.exists():
filepath.unlink() # Eliminar archivo
logger.info(f"Image deleted: {filename}")
return True
else:
logger.warning(f"Image not found for deletion: {filename}")
return False
except Exception as e:
logger.error(f"Error deleting image {filename}: {e}")
return False
def get_image_url(self, filename: str) -> str:
"""Genera la URL pública para una imagen"""
if not filename:
return None
return f"/images/{filename}"
# Instancia global
image_storage = ImageStorageManager()