fix: admin voit toutes les images, ajout images:delete par défaut, filtre client_id pour admin
This commit is contained in:
+26
-19
@@ -40,18 +40,17 @@ router = APIRouter(prefix="/images", tags=["Images"])
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
async def get_image_or_404(
|
||||
image_id: int, client_id: str, db: AsyncSession
|
||||
image_id: int, client: APIClient, db: AsyncSession
|
||||
) -> Image:
|
||||
"""
|
||||
Récupère une image par ID en vérifiant qu'elle appartient au client.
|
||||
Récupère une image par ID. L'admin peut accéder à toutes les images.
|
||||
Les autres clients sont limités à leurs propres images.
|
||||
Lève HTTP 404 si introuvable ou si elle n'appartient pas au client.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(Image).where(
|
||||
Image.id == image_id,
|
||||
Image.client_id == client_id,
|
||||
)
|
||||
)
|
||||
query = select(Image).where(Image.id == image_id)
|
||||
if not client.has_scope("admin"):
|
||||
query = query.where(Image.client_id == client.id)
|
||||
result = await db.execute(query)
|
||||
image = result.scalar_one_or_none()
|
||||
if not image:
|
||||
raise HTTPException(status_code=404, detail="Image introuvable")
|
||||
@@ -159,11 +158,19 @@ async def list_images(
|
||||
tag: Optional[str] = Query(None, description="Filtrer par tag AI"),
|
||||
status_filter: Optional[ProcessingStatus] = Query(None, alias="status"),
|
||||
search: Optional[str] = Query(None, description="Recherche dans description et OCR"),
|
||||
client_id: Optional[str] = Query(None, description="Filtrer par client (admin seulement)"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
# Filtre d'isolation par client
|
||||
query = select(Image).where(Image.client_id == client.id)
|
||||
# Admin : voit toutes les images, ou filtrées par client_id
|
||||
if client.has_scope("admin"):
|
||||
if client_id:
|
||||
query = select(Image).where(Image.client_id == client_id)
|
||||
else:
|
||||
query = select(Image)
|
||||
else:
|
||||
# Filtre d'isolation par client
|
||||
query = select(Image).where(Image.client_id == client.id)
|
||||
|
||||
if status_filter:
|
||||
query = query.where(Image.processing_status == status_filter)
|
||||
@@ -242,7 +249,7 @@ async def get_image(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
return ImageDetail.from_orm_full(image)
|
||||
|
||||
|
||||
@@ -262,7 +269,7 @@ async def get_status(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
return StatusResponse(
|
||||
id=image.id,
|
||||
@@ -288,7 +295,7 @@ async def get_exif(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
return {
|
||||
"id": image.id,
|
||||
@@ -333,7 +340,7 @@ async def get_ocr(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
return {
|
||||
"id": image.id,
|
||||
@@ -358,7 +365,7 @@ async def get_ai(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
return {
|
||||
"id": image.id,
|
||||
@@ -425,7 +432,7 @@ async def reprocess_image(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
# Reset du statut
|
||||
image.processing_status = ProcessingStatus.PENDING
|
||||
@@ -487,7 +494,7 @@ async def delete_image(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
client: APIClient = Depends(get_current_client),
|
||||
):
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
|
||||
# Décrémentation du quota
|
||||
file_size = image.file_size or 0
|
||||
@@ -524,7 +531,7 @@ async def get_download_url(
|
||||
):
|
||||
from app.services.storage_backend import get_storage_backend
|
||||
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
backend = get_storage_backend()
|
||||
|
||||
url = await backend.get_signed_url(image.file_path, expires_in=expires_in)
|
||||
@@ -545,7 +552,7 @@ async def get_thumbnail_url(
|
||||
):
|
||||
from app.services.storage_backend import get_storage_backend
|
||||
|
||||
image = await get_image_or_404(image_id, client.id, db)
|
||||
image = await get_image_or_404(image_id, client, db)
|
||||
if not image.thumbnail_path:
|
||||
raise HTTPException(status_code=404, detail="Thumbnail non disponible")
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ class ClientCreate(BaseModel):
|
||||
"""Créer un nouveau client API."""
|
||||
name: str = Field(..., min_length=1, max_length=256, description="Nom de l'application cliente")
|
||||
scopes: List[str] = Field(
|
||||
default=["images:read", "images:write"],
|
||||
default=["images:read", "images:write", "images:delete"],
|
||||
description="Permissions accordées",
|
||||
)
|
||||
plan: ClientPlan = Field(default=ClientPlan.FREE, description="Plan tarifaire")
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function Clients() {
|
||||
// Form state
|
||||
const [formName, setFormName] = useState('');
|
||||
const [formPlan, setFormPlan] = useState<Plan>('free');
|
||||
const [formScopes, setFormScopes] = useState<string[]>(['images:read', 'images:write']);
|
||||
const [formScopes, setFormScopes] = useState<string[]>(['images:read', 'images:write', 'images:delete']);
|
||||
|
||||
const filtered = clients?.filter((c) => {
|
||||
if (search && !c.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
|
||||
Reference in New Issue
Block a user