The backend no longer mentions MinIO anywhere: MinioClient -> GarageClient (minio_client.py -> garage_client.py), MINIO_* env vars -> GARAGE_*, and the `minio` Python package is replaced with `boto3` (AWS's generic S3 client) since Garage is the only storage backend this project targets now. S3 API semantics are unchanged, so document_service.py's behavior is identical — this is a naming and dependency swap, not a logic change. Also updates test fixtures/names (FakeMinioClient -> FakeGarageClient, FakeMinioSDK -> FakeS3SDK) and storage/README.md, which no longer frames Garage as a MinIO replacement (that migration note has served its purpose now that no MinIO reference remains in the codebase). Backend: 54/54 tests passing. Frontend: 38/38 tests passing (unaffected, verified for regressions since it talks to the backend only through the unchanged REST API).
51 lines
1.8 KiB
Python
51 lines
1.8 KiB
Python
import io
|
|
|
|
from botocore.exceptions import ClientError
|
|
|
|
|
|
class GarageClient:
|
|
def __init__(self, client, bucket: str):
|
|
self._client = client
|
|
self._bucket = bucket
|
|
|
|
def list_objects(self, prefix: str) -> list[dict]:
|
|
paginator = self._client.get_paginator("list_objects_v2")
|
|
result = []
|
|
for page in paginator.paginate(Bucket=self._bucket, Prefix=prefix, Delimiter="/"):
|
|
for common_prefix in page.get("CommonPrefixes", []):
|
|
result.append({
|
|
"key": common_prefix["Prefix"],
|
|
"size": None,
|
|
"is_dir": True,
|
|
"modifiedAt": None,
|
|
})
|
|
for obj in page.get("Contents", []):
|
|
result.append({
|
|
"key": obj["Key"],
|
|
"size": obj["Size"],
|
|
"is_dir": obj["Key"].endswith("/"),
|
|
"modifiedAt": obj["LastModified"].isoformat() if obj.get("LastModified") else None,
|
|
})
|
|
return result
|
|
|
|
def get_object(self, key: str) -> bytes:
|
|
try:
|
|
response = self._client.get_object(Bucket=self._bucket, Key=key)
|
|
except ClientError as exc:
|
|
if exc.response.get("Error", {}).get("Code") in ("NoSuchKey", "404"):
|
|
raise FileNotFoundError(key) from exc
|
|
raise
|
|
return response["Body"].read()
|
|
|
|
def put_object(self, key: str, data: bytes, content_type: str) -> None:
|
|
self._client.put_object(
|
|
Bucket=self._bucket,
|
|
Key=key,
|
|
Body=io.BytesIO(data),
|
|
ContentLength=len(data),
|
|
ContentType=content_type,
|
|
)
|
|
|
|
def delete_object(self, key: str) -> None:
|
|
self._client.delete_object(Bucket=self._bucket, Key=key)
|