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
+1
View File
@@ -0,0 +1 @@
"""Resource modules for the Imago SDK."""
+25
View File
@@ -0,0 +1,25 @@
"""
AI endpoints interaction.
"""
from typing import Any, Dict
from httpx import AsyncClient
from ..utils import with_retry
class AIResource:
"""Resource defining AI generative operations."""
def __init__(self, client: AsyncClient):
self._client = client
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
async def _req():
resp = await self._client.request(method, path, **kwargs)
resp.raise_for_status()
return resp.json()
return await with_retry(_req)
async def summarize_url(self, url: str, language: str = "français") -> Dict[str, Any]:
return await self._handle_request("POST", "/api/v1/ai/summarize", json={"url": url, "language": language})
async def draft_task(self, description: str, context: str = "") -> Dict[str, Any]:
return await self._handle_request("POST", "/api/v1/ai/draft-task", json={"description": description, "context": context})
+34
View File
@@ -0,0 +1,34 @@
"""
Client API keys management (requires admin scope)
"""
from typing import Any, Dict
from httpx import AsyncClient
from ..utils import with_retry
class AuthResource:
"""Resource for administering client accounts (admin only)."""
def __init__(self, client: AsyncClient):
self._client = client
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
async def _req():
resp = await self._client.request(method, path, **kwargs)
resp.raise_for_status()
return resp.json()
return await with_retry(_req)
async def create_client(self, name: str, is_premium: bool = False, scopes: list[str] = None) -> Dict[str, Any]:
data = {"name": name, "is_premium": is_premium}
if scopes:
data["scopes"] = scopes
return await self._handle_request("POST", "/api/v1/auth/clients", json=data)
async def list_clients(self) -> list[Dict[str, Any]]:
return await self._handle_request("GET", "/api/v1/auth/clients")
async def rotate_key(self, client_id: str) -> Dict[str, str]:
return await self._handle_request("POST", f"/api/v1/auth/clients/{client_id}/rotate-key")
async def delete_client(self, client_id: str) -> None:
await self._handle_request("DELETE", f"/api/v1/auth/clients/{client_id}")
+63
View File
@@ -0,0 +1,63 @@
"""
Client module for interacting with Imago API resources.
"""
from typing import Any, Dict, Optional, List
import io
from httpx import AsyncClient, Response
from ..models import HubImage
from ..websocket import PipelineStream
from ..utils import with_retry
class ImagesResource:
"""Resource defining image operations in the Imago API."""
def __init__(self, client: AsyncClient):
self._client = client
self.ws_url = f"{self._client.base_url}"
async def _handle_request(self, method: str, path: str, **kwargs) -> Any:
async def _req():
resp = await self._client.request(method, path, **kwargs)
resp.raise_for_status()
return resp.json()
return await with_retry(_req)
async def list(self, page: int = 1, size: int = 50, tags: Optional[str] = None) -> Dict[str, Any]:
params: dict = {"page": page, "size": size}
if tags is not None:
params["tags"] = tags
return await self._handle_request("GET", "/api/v1/images", params=params)
async def get(self, image_id: int) -> HubImage:
data = await self._handle_request("GET", f"/api/v1/images/{image_id}")
return HubImage.from_dict(data)
async def get_status(self, image_id: int) -> Dict[str, Any]:
return await self._handle_request("GET", f"/api/v1/images/{image_id}/status")
async def upload(self, file_content: bytes, filename: str, content_type: str = "image/jpeg") -> HubImage:
files = {"file": (filename, file_content, content_type)}
data = await self._handle_request("POST", "/api/v1/images/upload", files=files)
return HubImage.from_dict(data)
async def delete(self, image_id: int) -> Dict[str, Any]:
return await self._handle_request("DELETE", f"/api/v1/images/{image_id}")
async def get_all_tags(self) -> List[str]:
return await self._handle_request("GET", "/api/v1/images/tags/all")
async def reprocess(self, image_id: int) -> Dict[str, Any]:
return await self._handle_request("POST", f"/api/v1/images/{image_id}/reprocess")
async def get_download_url(self, image_id: int) -> str:
data = await self._handle_request("GET", f"/api/v1/images/{image_id}/download-url")
return data.get("download_url")
async def get_thumbnail_url(self, image_id: int) -> str:
data = await self._handle_request("GET", f"/api/v1/images/{image_id}/thumbnail-url")
return data.get("thumbnail_url")
def stream_pipeline(self, image_id: int, api_key: str) -> PipelineStream:
"""Returns a PipelineStream object for tracking the pipeline status in real-time."""
return PipelineStream(self.ws_url, api_key, image_id)