"""MinIO / S3 presigned URL helper. We use boto3 because MinIO speaks the S3 protocol and boto3 is the de-facto client. Generating presigned multipart upload URLs lets the browser upload files >100MB directly to MinIO without going through the FastAPI process, which avoids body-size limits and streaming-into-RAM issues. """ import boto3 from botocore.client import Config from ..config import settings def s3_client(): return boto3.client( "s3", endpoint_url=("https" if settings.minio_secure else "http") + "://" + settings.minio_endpoint, aws_access_key_id=settings.minio_access_key, aws_secret_access_key=settings.minio_secret_key, region_name=settings.minio_region, config=Config(signature_version="s3v4", s3={"addressing_style": "path"}), ) def ensure_bucket() -> None: client = s3_client() try: client.head_bucket(Bucket=settings.minio_bucket) except Exception: client.create_bucket(Bucket=settings.minio_bucket) def presign_put(object_key: str, content_type: str, expires: int = 3600) -> str: """Single-PUT presigned URL — for files small enough not to need multipart. For multipart (files >100MB), the frontend should use the AWS SDK's @aws-sdk/lib-storage Upload helper, which can sign each part itself once we hand it the credentials. For MVP we keep things simple with single-PUT + a 500 MB cap. """ return s3_client().generate_presigned_url( "put_object", Params={ "Bucket": settings.minio_bucket, "Key": object_key, "ContentType": content_type, }, ExpiresIn=expires, HttpMethod="PUT", ) def presign_get(object_key: str, expires: int = 3600) -> str: return s3_client().generate_presigned_url( "get_object", Params={"Bucket": settings.minio_bucket, "Key": object_key}, ExpiresIn=expires, )