Upgrade configuration and Docker stack to v2.0.0
This commit is contained in:
+35
-26
@@ -2,6 +2,7 @@
|
||||
Service de stockage — sauvegarde fichiers, génération thumbnails
|
||||
Multi-tenant : les fichiers sont isolés par client_id.
|
||||
"""
|
||||
import asyncio
|
||||
import uuid
|
||||
import logging
|
||||
import io
|
||||
@@ -33,6 +34,9 @@ async def save_upload(file: UploadFile, client_id: str) -> dict:
|
||||
"""
|
||||
Valide, sauvegarde le fichier uploadé et génère un thumbnail.
|
||||
Utilise le backend de stockage configuré (Local ou S3).
|
||||
|
||||
L'ordre est : validation → upload fichier → génération thumbnail → retourne les métadonnées.
|
||||
L'appelant est responsable de l'insertion BDD et du rollback en cas d'échec.
|
||||
"""
|
||||
# ── Validation MIME ───────────────────────────────────────
|
||||
if file.content_type not in ALLOWED_MIME_TYPES:
|
||||
@@ -53,7 +57,7 @@ async def save_upload(file: UploadFile, client_id: str) -> dict:
|
||||
|
||||
# ── Nommage ───────────────────────────────────────────────
|
||||
filename, file_uuid = _generate_filename(file.filename or "image")
|
||||
|
||||
|
||||
# Chemins relatifs par rapport au bucket/base_dir
|
||||
rel_file_path = f"uploads/{client_id}/{filename}"
|
||||
rel_thumb_path = f"thumbnails/{client_id}/thumb_{filename}"
|
||||
@@ -67,26 +71,28 @@ async def save_upload(file: UploadFile, client_id: str) -> dict:
|
||||
width, height = None, None
|
||||
thumb_saved = False
|
||||
try:
|
||||
# On utilise io.BytesIO pour ne pas avoir à écrire sur le disque local
|
||||
with PILImage.open(io.BytesIO(content)) as img:
|
||||
width, height = img.size
|
||||
img.thumbnail(THUMBNAIL_SIZE, PILImage.LANCZOS)
|
||||
|
||||
|
||||
# Convertit en RGB si nécessaire
|
||||
if img.mode in ("RGBA", "P"):
|
||||
img = img.convert("RGB")
|
||||
|
||||
|
||||
# Sauvegarde thumbnail dans un buffer
|
||||
thumb_buffer = io.BytesIO()
|
||||
img.save(thumb_buffer, "JPEG", quality=85)
|
||||
thumb_data = thumb_buffer.getvalue()
|
||||
|
||||
|
||||
# Sauvegarde via le backend
|
||||
await backend.save(thumb_data, rel_thumb_path, "image/jpeg")
|
||||
thumb_saved = True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Erreur génération thumbnail : %s", e)
|
||||
logger.warning("thumbnail.generation_error", extra={
|
||||
"file": rel_file_path,
|
||||
"error": str(e),
|
||||
})
|
||||
|
||||
return {
|
||||
"uuid": file_uuid,
|
||||
@@ -103,24 +109,27 @@ async def save_upload(file: UploadFile, client_id: str) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def delete_files(file_path: str, thumbnail_path: str | None = None) -> None:
|
||||
"""Supprime le fichier original et son thumbnail via le backend."""
|
||||
import asyncio
|
||||
async def delete_files(file_path: str, thumbnail_path: str | None = None) -> None:
|
||||
"""
|
||||
Supprime le fichier original et son thumbnail via le backend de stockage.
|
||||
Entièrement asynchrone — les fichiers sont supprimés de façon déterministe.
|
||||
"""
|
||||
backend = get_storage_backend()
|
||||
|
||||
async def _do_delete():
|
||||
await backend.delete(file_path)
|
||||
if thumbnail_path:
|
||||
await backend.delete(thumbnail_path)
|
||||
|
||||
# Note: delete_files est synchrone dans les routers existants,
|
||||
# mais le backend est async. C'est un risque.
|
||||
# TODO: Refactorer delete_image pour être full async.
|
||||
errors: list[str] = []
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
asyncio.ensure_future(_do_delete())
|
||||
else:
|
||||
loop.run_until_complete(_do_delete())
|
||||
except Exception:
|
||||
pass
|
||||
await backend.delete(file_path)
|
||||
except Exception as e:
|
||||
errors.append(f"original: {e}")
|
||||
|
||||
if thumbnail_path:
|
||||
try:
|
||||
await backend.delete(thumbnail_path)
|
||||
except Exception as e:
|
||||
errors.append(f"thumbnail: {e}")
|
||||
|
||||
if errors:
|
||||
logger.warning("storage.delete_partial", extra={
|
||||
"file_path": file_path,
|
||||
"errors": errors,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user