- Rename MinioConfig → S3Config, settings.minio → settings.s3 - Update config.example.yaml with R2 endpoint template - S3 service is now storage-backend agnostic (MinIO, R2, any S3-compatible) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.8 KiB
Python
67 lines
1.8 KiB
Python
import uuid
|
|
from datetime import datetime, timezone
|
|
from pathlib import PurePosixPath
|
|
|
|
import boto3
|
|
from botocore.config import Config as BotoConfig
|
|
|
|
from app.config import settings
|
|
|
|
|
|
def _get_client():
|
|
return boto3.client(
|
|
"s3",
|
|
endpoint_url=settings.s3.endpoint,
|
|
aws_access_key_id=settings.s3.access_key,
|
|
aws_secret_access_key=settings.s3.secret_key,
|
|
config=BotoConfig(signature_version="s3v4"),
|
|
)
|
|
|
|
|
|
def _generate_object_key(filename: str) -> str:
|
|
now = datetime.now(timezone.utc)
|
|
date_prefix = now.strftime("%Y/%m/%d")
|
|
ext = PurePosixPath(filename).suffix or ".jpg"
|
|
return f"{date_prefix}/{uuid.uuid4().hex}{ext}"
|
|
|
|
|
|
def upload_image(file_bytes: bytes, original_filename: str) -> str:
|
|
client = _get_client()
|
|
object_key = _generate_object_key(original_filename)
|
|
content_type = "image/jpeg"
|
|
if original_filename.lower().endswith(".png"):
|
|
content_type = "image/png"
|
|
|
|
client.put_object(
|
|
Bucket=settings.s3.bucket,
|
|
Key=object_key,
|
|
Body=file_bytes,
|
|
ContentType=content_type,
|
|
)
|
|
return object_key
|
|
|
|
|
|
def _get_presign_client():
|
|
"""Client using public endpoint so presigned URL signatures match the public host."""
|
|
endpoint = settings.s3.public_endpoint or settings.s3.endpoint
|
|
return boto3.client(
|
|
"s3",
|
|
endpoint_url=endpoint,
|
|
aws_access_key_id=settings.s3.access_key,
|
|
aws_secret_access_key=settings.s3.secret_key,
|
|
config=BotoConfig(signature_version="s3v4"),
|
|
)
|
|
|
|
|
|
def generate_presigned_put_url(object_key: str) -> str:
|
|
client = _get_presign_client()
|
|
url: str = client.generate_presigned_url(
|
|
"put_object",
|
|
Params={
|
|
"Bucket": settings.s3.bucket,
|
|
"Key": object_key,
|
|
},
|
|
ExpiresIn=300,
|
|
)
|
|
return url
|