feat: vue BibliothequeView + route /bibliotheque + nav + endpoint media/all
- backend: ajoute GET /api/media/all (filtrable par entity_type, trié par date desc) dans media.py ; importe Optional depuis typing - frontend: crée BibliothequeView.vue (grille photo, filtres par type, lightbox, modal PhotoIdentifyModal) - frontend: ajoute la route /bibliotheque dans router/index.ts - frontend: ajoute le lien "📷 Bibliothèque" dans AppDrawer.vue Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,29 +1,112 @@
|
||||
import os
|
||||
import uuid
|
||||
from fastapi import APIRouter, File, HTTPException, UploadFile
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.config import UPLOAD_DIR
|
||||
from app.database import get_session
|
||||
from app.models.media import Attachment, Media
|
||||
|
||||
router = APIRouter(tags=["media"])
|
||||
|
||||
ALLOWED_EXT = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
|
||||
|
||||
def _save_webp(data: bytes, max_px: int) -> str:
|
||||
try:
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
img = Image.open(io.BytesIO(data)).convert("RGB")
|
||||
img.thumbnail((max_px, max_px))
|
||||
name = f"{uuid.uuid4()}.webp"
|
||||
path = os.path.join(UPLOAD_DIR, name)
|
||||
img.save(path, "WEBP", quality=85)
|
||||
return name
|
||||
except Exception:
|
||||
name = f"{uuid.uuid4()}.bin"
|
||||
path = os.path.join(UPLOAD_DIR, name)
|
||||
with open(path, "wb") as f:
|
||||
f.write(data)
|
||||
return name
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_file(file: UploadFile = File(...)):
|
||||
ext = os.path.splitext(file.filename or "")[-1].lower()
|
||||
if ext not in ALLOWED_EXT:
|
||||
raise HTTPException(status_code=400, detail="Format non supporté")
|
||||
filename = f"{uuid.uuid4().hex}{ext}"
|
||||
dest = os.path.join(UPLOAD_DIR, filename)
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
content = await file.read()
|
||||
with open(dest, "wb") as f:
|
||||
f.write(content)
|
||||
return {"filename": filename, "url": f"/uploads/{filename}"}
|
||||
data = await file.read()
|
||||
ct = file.content_type or ""
|
||||
if ct.startswith("image/"):
|
||||
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}
|
||||
|
||||
|
||||
@router.delete("/upload/{filename}", status_code=204)
|
||||
def delete_file(filename: str):
|
||||
path = os.path.join(UPLOAD_DIR, filename)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
@router.get("/media/all", response_model=List[Media])
|
||||
def list_all_media(
|
||||
entity_type: Optional[str] = Query(default=None),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""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()
|
||||
|
||||
|
||||
@router.get("/media", response_model=List[Media])
|
||||
def list_media(
|
||||
entity_type: str = Query(...),
|
||||
entity_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
return session.exec(
|
||||
select(Media).where(
|
||||
Media.entity_type == entity_type, Media.entity_id == entity_id
|
||||
)
|
||||
).all()
|
||||
|
||||
|
||||
@router.post("/media", response_model=Media, status_code=status.HTTP_201_CREATED)
|
||||
def create_media(m: Media, session: Session = Depends(get_session)):
|
||||
session.add(m)
|
||||
session.commit()
|
||||
session.refresh(m)
|
||||
return m
|
||||
|
||||
|
||||
@router.delete("/media/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_media(id: int, session: Session = Depends(get_session)):
|
||||
m = session.get(Media, id)
|
||||
if not m:
|
||||
raise HTTPException(404, "Media introuvable")
|
||||
session.delete(m)
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.get("/attachments", response_model=List[Attachment])
|
||||
def list_attachments(
|
||||
entity_type: str = Query(...),
|
||||
entity_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
return session.exec(
|
||||
select(Attachment).where(
|
||||
Attachment.entity_type == entity_type,
|
||||
Attachment.entity_id == entity_id,
|
||||
)
|
||||
).all()
|
||||
|
||||
|
||||
@router.post("/attachments", response_model=Attachment, status_code=status.HTTP_201_CREATED)
|
||||
def create_attachment(a: Attachment, session: Session = Depends(get_session)):
|
||||
session.add(a)
|
||||
session.commit()
|
||||
session.refresh(a)
|
||||
return a
|
||||
|
||||
Reference in New Issue
Block a user