Feat: Proxy image content through the API instead of redirecting to MinIO
Replace the presigned-URL redirect (302) in GET /api/v1/images/{id}/file
with a direct proxy that fetches bytes from S3 server-side and returns them
to the client. The browser never contacts the storage backend, eliminating
the /etc/hosts workaround needed in local development.
- StorageBackend: swap get_presigned_url for get(key) -> bytes
- S3StorageBackend: implement get() via aiobotocore get_object
- serve_image_file: return Response with ETag + Cache-Control: immutable
- test_serving: assert 200 + content-type + ETag; add no-storage-details test
- Spec Kit artifacts for feature 002-api-image-proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,14 +32,10 @@ class S3StorageBackend(StorageBackend):
|
||||
ContentType=content_type,
|
||||
)
|
||||
|
||||
async def get_presigned_url(self, key: str, expires_in_seconds: int = 3600) -> str:
|
||||
async def get(self, key: str) -> bytes:
|
||||
async with self._client() as client:
|
||||
url = await client.generate_presigned_url(
|
||||
"get_object",
|
||||
Params={"Bucket": self._settings.s3_bucket_name, "Key": key},
|
||||
ExpiresIn=expires_in_seconds,
|
||||
)
|
||||
return url
|
||||
response = await client.get_object(Bucket=self._settings.s3_bucket_name, Key=key)
|
||||
return await response["Body"].read()
|
||||
|
||||
async def delete(self, key: str) -> None:
|
||||
async with self._client() as client:
|
||||
|
||||
Reference in New Issue
Block a user