Add admin panel, WebSocket support, and API versioning
CI / Lint & Format (push) Failing after 28s
CI / Tests (push) Has been skipped
CI / Security Scan (push) Failing after 8s
CI / Docker Build (push) Has been skipped

Introduce an admin portal (React + Nginx), WebSocket routing, and
API versioning middleware with `/api/v1/` prefix deprecation.
Add master API key authentication, new Prometheus metrics for AI
token consumption and active WebSockets, and extend S3 config
with a public endpoint URL. Update test paths and fixtures to
align with the new routing structure.
This commit is contained in:
2026-06-22 11:25:22 -04:00
parent d68deb9c74
commit 2f2a5e9b4a
158 changed files with 64811 additions and 503 deletions
+6 -6
View File
@@ -31,7 +31,7 @@ async def test_get_image_exif(async_client: AsyncClient, client_a, auth_headers_
await db_session.commit()
await db_session.refresh(image)
response = await async_client.get(f"/images/{image.id}/exif", headers=auth_headers_a)
response = await async_client.get(f"/api/v1/images/{image.id}/exif", headers=auth_headers_a)
assert response.status_code == 200
data = response.json()
assert data["camera"]["make"] == "Apple"
@@ -52,7 +52,7 @@ async def test_get_image_ocr(async_client: AsyncClient, client_a, auth_headers_a
await db_session.commit()
await db_session.refresh(image)
response = await async_client.get(f"/images/{image.id}/ocr", headers=auth_headers_a)
response = await async_client.get(f"/api/v1/images/{image.id}/ocr", headers=auth_headers_a)
assert response.status_code == 200
data = response.json()
assert data["has_text"] is True
@@ -72,7 +72,7 @@ async def test_get_image_ai(async_client: AsyncClient, client_a, auth_headers_a,
await db_session.commit()
await db_session.refresh(image)
response = await async_client.get(f"/images/{image.id}/ai", headers=auth_headers_a)
response = await async_client.get(f"/api/v1/images/{image.id}/ai", headers=auth_headers_a)
assert response.status_code == 200
data = response.json()
assert data["description"] == "A beautiful landscape"
@@ -85,7 +85,7 @@ async def test_get_all_tags(async_client: AsyncClient, client_a, auth_headers_a,
db_session.add(create_test_image(client_id=client_a.id, original_name="2.jpg", ai_tags=["tag2", "tag3"]))
await db_session.commit()
response = await async_client.get("/images/tags/all", headers=auth_headers_a)
response = await async_client.get("/api/v1/images/tags/all", headers=auth_headers_a)
assert response.status_code == 200
data = response.json()
assert set(data["tags"]) == {"tag1", "tag2", "tag3"}
@@ -102,7 +102,7 @@ async def test_reprocess_image(async_client: AsyncClient, client_a, auth_headers
await db_session.refresh(image)
# Le ARQ pool est mocké globalement dans conftest.py
response = await async_client.post(f"/images/{image.id}/reprocess", headers=auth_headers_a)
response = await async_client.post(f"/api/v1/images/{image.id}/reprocess", headers=auth_headers_a)
assert response.status_code == 200
await db_session.refresh(image)
@@ -120,7 +120,7 @@ async def test_delete_image(async_client: AsyncClient, client_a, auth_headers_a,
await db_session.refresh(image)
with patch("app.routers.images.storage.delete_files") as mock_delete:
response = await async_client.delete(f"/images/{image.id}", headers=auth_headers_a)
response = await async_client.delete(f"/api/v1/images/{image.id}", headers=auth_headers_a)
assert response.status_code == 200
mock_delete.assert_called_once_with("fake/path.jpg", "fake/thumb.jpg")