222 lines
6.7 KiB
Python
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)
|