Files
Cellar---Alcools-collection/backend/routers/drinks.py
T
2026-06-26 11:54:29 +02:00

222 lines
6.7 KiB
Python

import io
import os
import uuid
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
from fastapi.responses import FileResponse
from PIL import Image
from sqlalchemy import or_
from sqlalchemy.orm import Session
from auth import get_current_user, get_user_from_token
from database import get_db
from models import Drink, DrinkCategory, User
from schemas import DrinkCreate, DrinkResponse, DrinkUpdate
router = APIRouter(prefix="/api/drinks", tags=["drinks"])
UPLOAD_DIR = os.path.join(os.path.dirname(__file__), "..", "uploads")
os.makedirs(UPLOAD_DIR, exist_ok=True)
ALLOWED_MIMES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
MAX_UPLOAD_SIZE = 10 * 1024 * 1024 # 10 MB
def get_user_drink(drink_id: int, user: User, db: Session) -> Drink:
drink = db.query(Drink).filter(Drink.id == drink_id, Drink.owner_id == user.id).first()
if not drink:
raise HTTPException(status_code=404, detail="Drink not found")
return drink
@router.post("", response_model=DrinkResponse)
async def create_drink(
drink: DrinkCreate,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
db_drink = Drink(**drink.model_dump(), owner_id=user.id)
db.add(db_drink)
db.commit()
db.refresh(db_drink)
return db_drink
@router.get("", response_model=list[DrinkResponse])
def list_drinks(
category: DrinkCategory | None = None,
search: str | None = None,
min_rating: float | None = Query(None, ge=0, le=5),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
query = db.query(Drink).filter(Drink.owner_id == user.id)
if category:
query = query.filter(Drink.category == category)
if search:
search_term = f"%{search}%"
query = query.filter(
or_(
Drink.name.ilike(search_term),
Drink.region.ilike(search_term),
Drink.producer.ilike(search_term),
Drink.brewery.ilike(search_term),
Drink.distillery.ilike(search_term),
Drink.grape_variety.ilike(search_term),
Drink.country.ilike(search_term),
Drink.notes.ilike(search_term),
)
)
if min_rating is not None:
query = query.filter(Drink.rating >= min_rating)
return query.order_by(Drink.created_at.desc()).offset(skip).limit(limit).all()
@router.get("/{drink_id}", response_model=DrinkResponse)
def get_drink(
drink_id: int,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
return get_user_drink(drink_id, user, db)
@router.put("/{drink_id}", response_model=DrinkResponse)
def update_drink(
drink_id: int,
updates: DrinkUpdate,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
drink = get_user_drink(drink_id, user, db)
update_data = updates.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(drink, field, value)
db.commit()
db.refresh(drink)
return drink
@router.delete("/{drink_id}")
def delete_drink(
drink_id: int,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
drink = get_user_drink(drink_id, user, db)
if drink.image_path:
filepath = os.path.join(os.path.dirname(__file__), "..", drink.image_path)
resolved = os.path.realpath(filepath)
if resolved.startswith(os.path.realpath(UPLOAD_DIR)) and os.path.exists(resolved):
os.remove(resolved)
db.delete(drink)
db.commit()
return {"detail": "Drink deleted"}
@router.post("/{drink_id}/upload-image", response_model=DrinkResponse)
async def upload_image(
drink_id: int,
file: UploadFile = File(...),
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
drink = get_user_drink(drink_id, user, db)
if file.content_type not in ALLOWED_MIMES:
raise HTTPException(
status_code=400,
detail=f"Type de fichier non autorisé. Utilisez: {', '.join(ALLOWED_MIMES)}",
)
ext = os.path.splitext(file.filename or "")[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"Extension non autorisée. Utilisez: {', '.join(ALLOWED_EXTENSIONS)}",
)
content = await file.read()
if len(content) > MAX_UPLOAD_SIZE:
raise HTTPException(
status_code=400,
detail=f"Fichier trop volumineux. Taille maximale: {MAX_UPLOAD_SIZE // (1024 * 1024)} MB",
)
try:
img = Image.open(io.BytesIO(content))
img.verify()
real_format = img.format.lower() if img.format else ""
if real_format not in {"jpeg", "png", "webp", "gif"}:
raise HTTPException(
status_code=400,
detail=f"Format d'image non autorisé: {real_format}. Utilisez: JPEG, PNG, WebP ou GIF",
)
except HTTPException:
raise
except Exception:
raise HTTPException(
status_code=400,
detail="Le fichier n'est pas une image valide",
)
filename = f"{uuid.uuid4().hex}{ext}"
filepath = os.path.join(UPLOAD_DIR, filename)
with open(filepath, "wb") as f:
f.write(content)
if drink.image_path:
old_path = os.path.join(os.path.dirname(__file__), "..", drink.image_path)
resolved = os.path.realpath(old_path)
if resolved.startswith(os.path.realpath(UPLOAD_DIR)) and os.path.exists(resolved):
os.remove(resolved)
drink.image_path = f"uploads/{filename}"
db.commit()
db.refresh(drink)
return drink
MIME_MAP = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".webp": "image/webp",
".gif": "image/gif",
}
@router.get("/{drink_id}/image")
def get_drink_image(
drink_id: int,
token: str = Query(...),
db: Session = Depends(get_db),
):
user = get_user_from_token(token, db)
drink = db.query(Drink).filter(Drink.id == drink_id, Drink.owner_id == user.id).first()
if not drink or not drink.image_path:
raise HTTPException(status_code=404, detail="Image non trouvée")
filename = os.path.basename(drink.image_path)
filepath = os.path.realpath(os.path.join(UPLOAD_DIR, filename))
if not filepath.startswith(os.path.realpath(UPLOAD_DIR)) or not os.path.isfile(filepath):
raise HTTPException(status_code=404, detail="Image non trouvée")
ext = os.path.splitext(filename)[1].lower()
media_type = MIME_MAP.get(ext, "application/octet-stream")
return FileResponse(filepath, media_type=media_type)