before gemiin
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
import unicodedata
|
||||
import uuid
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, File, HTTPException, Query, UploadFile, status
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session, select
|
||||
|
||||
@@ -16,9 +17,91 @@ class MediaPatch(BaseModel):
|
||||
entity_id: Optional[int] = None
|
||||
titre: Optional[str] = None
|
||||
|
||||
|
||||
CANONICAL_ENTITY_TYPES = {
|
||||
"jardin",
|
||||
"plante",
|
||||
"adventice",
|
||||
"outil",
|
||||
"plantation",
|
||||
"bibliotheque",
|
||||
}
|
||||
|
||||
ENTITY_TYPE_ALIASES = {
|
||||
# Canonique
|
||||
"jardin": "jardin",
|
||||
"plante": "plante",
|
||||
"adventice": "adventice",
|
||||
"outil": "outil",
|
||||
"plantation": "plantation",
|
||||
"bibliotheque": "bibliotheque",
|
||||
# Variantes FR
|
||||
"jardins": "jardin",
|
||||
"plantes": "plante",
|
||||
"adventices": "adventice",
|
||||
"outils": "outil",
|
||||
"plantations": "plantation",
|
||||
"bibliotheques": "bibliotheque",
|
||||
"bibliotheque_media": "bibliotheque",
|
||||
# Variantes EN (courantes via API)
|
||||
"garden": "jardin",
|
||||
"gardens": "jardin",
|
||||
"plant": "plante",
|
||||
"plants": "plante",
|
||||
"weed": "adventice",
|
||||
"weeds": "adventice",
|
||||
"tool": "outil",
|
||||
"tools": "outil",
|
||||
"planting": "plantation",
|
||||
"plantings": "plantation",
|
||||
"library": "bibliotheque",
|
||||
"media_library": "bibliotheque",
|
||||
}
|
||||
|
||||
router = APIRouter(tags=["media"])
|
||||
|
||||
|
||||
def _normalize_token(value: str) -> str:
|
||||
token = (value or "").strip().lower()
|
||||
token = unicodedata.normalize("NFKD", token).encode("ascii", "ignore").decode("ascii")
|
||||
return token.replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _normalize_entity_type(value: str, *, strict: bool = True) -> str:
|
||||
token = _normalize_token(value)
|
||||
canonical = ENTITY_TYPE_ALIASES.get(token, token)
|
||||
if canonical in CANONICAL_ENTITY_TYPES:
|
||||
return canonical
|
||||
if strict:
|
||||
allowed = ", ".join(sorted(CANONICAL_ENTITY_TYPES))
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=f"entity_type invalide: '{value}'. Valeurs autorisees: {allowed}",
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _entity_type_candidates(value: str) -> set[str]:
|
||||
canonical = _normalize_entity_type(value, strict=True)
|
||||
candidates = {canonical}
|
||||
for alias, target in ENTITY_TYPE_ALIASES.items():
|
||||
if target == canonical:
|
||||
candidates.add(alias)
|
||||
return candidates
|
||||
|
||||
|
||||
def _canonicalize_rows(rows: List[Media], session: Session) -> None:
|
||||
changed = False
|
||||
for media in rows:
|
||||
normalized = _normalize_entity_type(media.entity_type, strict=False)
|
||||
if normalized in CANONICAL_ENTITY_TYPES and normalized != media.entity_type:
|
||||
media.entity_type = normalized
|
||||
session.add(media)
|
||||
changed = True
|
||||
if changed:
|
||||
session.commit()
|
||||
|
||||
|
||||
def _save_webp(data: bytes, max_px: int) -> str:
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -47,12 +130,12 @@ async def upload_file(file: UploadFile = File(...)):
|
||||
name = _save_webp(data, 1200)
|
||||
thumb = _save_webp(data, 300)
|
||||
return {"url": f"/uploads/{name}", "thumbnail_url": f"/uploads/{thumb}"}
|
||||
else:
|
||||
name = f"{uuid.uuid4()}_{file.filename}"
|
||||
path = os.path.join(UPLOAD_DIR, name)
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
return {"url": f"/uploads/{name}", "thumbnail_url": None}
|
||||
|
||||
name = f"{uuid.uuid4()}_{file.filename}"
|
||||
path = os.path.join(UPLOAD_DIR, name)
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
return {"url": f"/uploads/{name}", "thumbnail_url": None}
|
||||
|
||||
|
||||
@router.get("/media/all", response_model=List[Media])
|
||||
@@ -63,8 +146,10 @@ def list_all_media(
|
||||
"""Retourne tous les médias, filtrés optionnellement par entity_type."""
|
||||
q = select(Media).order_by(Media.created_at.desc())
|
||||
if entity_type:
|
||||
q = q.where(Media.entity_type == entity_type)
|
||||
return session.exec(q).all()
|
||||
q = q.where(Media.entity_type.in_(_entity_type_candidates(entity_type)))
|
||||
rows = session.exec(q).all()
|
||||
_canonicalize_rows(rows, session)
|
||||
return rows
|
||||
|
||||
|
||||
@router.get("/media", response_model=List[Media])
|
||||
@@ -73,15 +158,19 @@ def list_media(
|
||||
entity_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
return session.exec(
|
||||
rows = session.exec(
|
||||
select(Media).where(
|
||||
Media.entity_type == entity_type, Media.entity_id == entity_id
|
||||
Media.entity_type.in_(_entity_type_candidates(entity_type)),
|
||||
Media.entity_id == entity_id,
|
||||
)
|
||||
).all()
|
||||
_canonicalize_rows(rows, session)
|
||||
return rows
|
||||
|
||||
|
||||
@router.post("/media", response_model=Media, status_code=status.HTTP_201_CREATED)
|
||||
def create_media(m: Media, session: Session = Depends(get_session)):
|
||||
m.entity_type = _normalize_entity_type(m.entity_type, strict=True)
|
||||
session.add(m)
|
||||
session.commit()
|
||||
session.refresh(m)
|
||||
@@ -93,7 +182,12 @@ def update_media(id: int, payload: MediaPatch, session: Session = Depends(get_se
|
||||
m = session.get(Media, id)
|
||||
if not m:
|
||||
raise HTTPException(404, "Media introuvable")
|
||||
for k, v in payload.model_dump(exclude_none=True).items():
|
||||
|
||||
updates = payload.model_dump(exclude_none=True)
|
||||
if "entity_type" in updates:
|
||||
updates["entity_type"] = _normalize_entity_type(updates["entity_type"], strict=True)
|
||||
|
||||
for k, v in updates.items():
|
||||
setattr(m, k, v)
|
||||
session.add(m)
|
||||
session.commit()
|
||||
|
||||
Reference in New Issue
Block a user