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:
2026-02-22 12:35:46 +01:00
parent 7349f770e6
commit 94ebe338a0
4 changed files with 271 additions and 20 deletions

View File

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