142 lines
4.6 KiB
Python
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
|