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(
|
async def get_image_or_404(
|
||||||
image_id: int, client_id: str, db: AsyncSession
|
image_id: int, client: APIClient, db: AsyncSession
|
||||||
) -> Image:
|
) -> 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.
|
Lève HTTP 404 si introuvable ou si elle n'appartient pas au client.
|
||||||
"""
|
"""
|
||||||
result = await db.execute(
|
query = select(Image).where(Image.id == image_id)
|
||||||
select(Image).where(
|
if not client.has_scope("admin"):
|
||||||
Image.id == image_id,
|
query = query.where(Image.client_id == client.id)
|
||||||
Image.client_id == client_id,
|
result = await db.execute(query)
|
||||||
)
|
|
||||||
)
|
|
||||||
image = result.scalar_one_or_none()
|
image = result.scalar_one_or_none()
|
||||||
if not image:
|
if not image:
|
||||||
raise HTTPException(status_code=404, detail="Image introuvable")
|
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"),
|
tag: Optional[str] = Query(None, description="Filtrer par tag AI"),
|
||||||
status_filter: Optional[ProcessingStatus] = Query(None, alias="status"),
|
status_filter: Optional[ProcessingStatus] = Query(None, alias="status"),
|
||||||
search: Optional[str] = Query(None, description="Recherche dans description et OCR"),
|
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),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
client: APIClient = Depends(get_current_client),
|
||||||
):
|
):
|
||||||
# Filtre d'isolation par client
|
# Admin : voit toutes les images, ou filtrées par client_id
|
||||||
query = select(Image).where(Image.client_id == 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:
|
if status_filter:
|
||||||
query = query.where(Image.processing_status == status_filter)
|
query = query.where(Image.processing_status == status_filter)
|
||||||
@@ -242,7 +249,7 @@ async def get_image(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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)
|
return ImageDetail.from_orm_full(image)
|
||||||
|
|
||||||
|
|
||||||
@@ -262,7 +269,7 @@ async def get_status(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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(
|
return StatusResponse(
|
||||||
id=image.id,
|
id=image.id,
|
||||||
@@ -288,7 +295,7 @@ async def get_exif(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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 {
|
return {
|
||||||
"id": image.id,
|
"id": image.id,
|
||||||
@@ -333,7 +340,7 @@ async def get_ocr(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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 {
|
return {
|
||||||
"id": image.id,
|
"id": image.id,
|
||||||
@@ -358,7 +365,7 @@ async def get_ai(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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 {
|
return {
|
||||||
"id": image.id,
|
"id": image.id,
|
||||||
@@ -425,7 +432,7 @@ async def reprocess_image(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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
|
# Reset du statut
|
||||||
image.processing_status = ProcessingStatus.PENDING
|
image.processing_status = ProcessingStatus.PENDING
|
||||||
@@ -487,7 +494,7 @@ async def delete_image(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
client: APIClient = Depends(get_current_client),
|
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
|
# Décrémentation du quota
|
||||||
file_size = image.file_size or 0
|
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
|
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()
|
backend = get_storage_backend()
|
||||||
|
|
||||||
url = await backend.get_signed_url(image.file_path, expires_in=expires_in)
|
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
|
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:
|
if not image.thumbnail_path:
|
||||||
raise HTTPException(status_code=404, detail="Thumbnail non disponible")
|
raise HTTPException(status_code=404, detail="Thumbnail non disponible")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ class ClientCreate(BaseModel):
|
|||||||
"""Créer un nouveau client API."""
|
"""Créer un nouveau client API."""
|
||||||
name: str = Field(..., min_length=1, max_length=256, description="Nom de l'application cliente")
|
name: str = Field(..., min_length=1, max_length=256, description="Nom de l'application cliente")
|
||||||
scopes: List[str] = Field(
|
scopes: List[str] = Field(
|
||||||
default=["images:read", "images:write"],
|
default=["images:read", "images:write", "images:delete"],
|
||||||
description="Permissions accordées",
|
description="Permissions accordées",
|
||||||
)
|
)
|
||||||
plan: ClientPlan = Field(default=ClientPlan.FREE, description="Plan tarifaire")
|
plan: ClientPlan = Field(default=ClientPlan.FREE, description="Plan tarifaire")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function Clients() {
|
|||||||
// Form state
|
// Form state
|
||||||
const [formName, setFormName] = useState('');
|
const [formName, setFormName] = useState('');
|
||||||
const [formPlan, setFormPlan] = useState<Plan>('free');
|
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) => {
|
const filtered = clients?.filter((c) => {
|
||||||
if (search && !c.name.toLowerCase().includes(search.toLowerCase())) return false;
|
if (search && !c.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user