before gemiin

This commit is contained in:
2026-02-22 22:18:32 +01:00
parent fb33540bb0
commit 9db5cbf236
147 changed files with 7948 additions and 531 deletions

View File

@@ -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()