Files
webcarto/backend/app/routes/images.py
2026-02-09 00:01:29 +01:00

142 lines
4.6 KiB
Python

import json
import base64
import re
import uuid
from pathlib import Path
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
from fastapi.responses import FileResponse
from sqlmodel import Session
from ..database import get_session
from ..models import Feature
from ..config import DATA_DIR
router = APIRouter(prefix="/images", tags=["images"])
IMAGES_DIR = DATA_DIR / "images"
@router.get("/{dataset_id}/{filename}")
def get_image(dataset_id: int, filename: str):
"""Servir une image stockée."""
path = IMAGES_DIR / str(dataset_id) / filename
if not path.exists() or not path.is_file():
raise HTTPException(404, "Image non trouvée")
# Sécurité : vérifier que le chemin résolu est bien dans IMAGES_DIR
if not path.resolve().is_relative_to(IMAGES_DIR.resolve()):
raise HTTPException(403, "Accès interdit")
media_type = "image/jpeg"
if filename.endswith(".png"):
media_type = "image/png"
elif filename.endswith(".webp"):
media_type = "image/webp"
return FileResponse(path, media_type=media_type)
@router.post("/features/{feature_id}")
async def upload_image(
feature_id: int,
file: UploadFile = File(...),
session: Session = Depends(get_session),
):
"""Uploader une nouvelle image pour une feature."""
feature = session.get(Feature, feature_id)
if not feature:
raise HTTPException(404, "Feature non trouvée")
props = json.loads(feature.properties_json)
images = props.get("_images", [])
# Sauvegarder le fichier
img_dir = IMAGES_DIR / str(feature.dataset_id)
img_dir.mkdir(parents=True, exist_ok=True)
ext = Path(file.filename).suffix or ".jpg"
filename = f"{feature_id}_{uuid.uuid4().hex[:8]}{ext}"
filepath = img_dir / filename
content = await file.read()
filepath.write_bytes(content)
# Ajouter l'URL dans les propriétés
url = f"/api/images/{feature.dataset_id}/{filename}"
images.append(url)
props["_images"] = images
feature.properties_json = json.dumps(props)
session.add(feature)
session.commit()
return {"url": url, "images": images}
@router.delete("/features/{feature_id}/{filename}")
def delete_image(
feature_id: int,
filename: str,
session: Session = Depends(get_session),
):
"""Supprimer une image d'une feature."""
feature = session.get(Feature, feature_id)
if not feature:
raise HTTPException(404, "Feature non trouvée")
props = json.loads(feature.properties_json)
images = props.get("_images", [])
# Trouver et supprimer l'URL correspondante
url = f"/api/images/{feature.dataset_id}/{filename}"
if url not in images:
raise HTTPException(404, "Image non trouvée dans cette feature")
images.remove(url)
props["_images"] = images
feature.properties_json = json.dumps(props)
session.add(feature)
session.commit()
# Supprimer le fichier
filepath = IMAGES_DIR / str(feature.dataset_id) / filename
if filepath.exists() and filepath.resolve().is_relative_to(IMAGES_DIR.resolve()):
filepath.unlink()
return {"images": images}
def extract_and_save_images(properties: dict, dataset_id: int, feature_index: int) -> dict:
"""Extraire les images base64 des propriétés et les sauvegarder en fichiers.
Les data URIs dans _images sont remplacées par des URLs /api/images/...
"""
images = properties.get("_images", [])
if not images:
return properties
img_dir = IMAGES_DIR / str(dataset_id)
img_dir.mkdir(parents=True, exist_ok=True)
saved_urls = []
for i, img in enumerate(images):
if isinstance(img, str) and img.startswith("data:image"):
# Extraire le base64
match = re.match(r"data:image/(\w+);base64,(.+)", img, re.DOTALL)
if match:
ext = match.group(1)
if ext == "jpeg":
ext = "jpg"
b64_data = match.group(2)
try:
raw = base64.b64decode(b64_data)
filename = f"{feature_index}_{i}.{ext}"
filepath = img_dir / filename
filepath.write_bytes(raw)
saved_urls.append(f"/api/images/{dataset_id}/{filename}")
except Exception:
continue
elif isinstance(img, str) and img.startswith("/api/images/"):
# Déjà une URL serveur
saved_urls.append(img)
elif isinstance(img, str) and img.startswith("http"):
# URL externe, garder telle quelle
saved_urls.append(img)
properties["_images"] = saved_urls
return properties