import boto3 from botocore.exceptions import ClientError from app.core.config import settings class StorageClient: def __init__(self): self.s3_client = boto3.client( "s3", endpoint_url=f"http://{settings.MINIO_ENDPOINT}", aws_access_key_id=settings.MINIO_ACCESS_KEY, aws_secret_access_key=settings.MINIO_SECRET_KEY, config=boto3.session.Config(signature_version='s3v4') ) self.bucket_name = settings.MINIO_BUCKET self._ensure_bucket_exists() def _ensure_bucket_exists(self): try: self.s3_client.head_bucket(Bucket=self.bucket_name) except ClientError: try: self.s3_client.create_bucket(Bucket=self.bucket_name) # Set bucket policy to public read if needed, or rely on presigned URLs # For now, we will rely on presigned URLs for security except ClientError as e: print(f"Could not create bucket {self.bucket_name}: {e}") def upload_file(self, file_obj, object_name: str, content_type: str = None) -> bool: try: extra_args = {} if content_type: extra_args["ContentType"] = content_type self.s3_client.upload_fileobj(file_obj, self.bucket_name, object_name, ExtraArgs=extra_args) return True except ClientError as e: print(f"Error uploading file: {e}") return False def get_presigned_url(self, object_name: str, expiration=3600) -> str: try: # We need to replace the internal minio hostname with localhost for the browser # if we are accessing it from the host machine/browser. # But the backend sees "minio". # This is tricky in docker-compose. # The client needs a URL that resolves. # Usually we use a proxy or just configure the endpoint on the frontend. # For now generate the URL and we might need to swap the host in the frontend or # ensure the backend generates a URL accessible to the user. # Actually, standard practice: Backend generates URL using its known endpoint. # If that endpoint is "minio:9000", the browser can't resolve it. # So we might need to override the endpoint for presigning. url = self.s3_client.generate_presigned_url( 'get_object', Params={'Bucket': self.bucket_name, 'Key': object_name}, ExpiresIn=expiration ) # Hack for localhost dev: replace minio:9000 with localhost:9000 # dependent on where the request comes from. # Ideally getting this from config would be better. return url.replace("http://minio:9000", "http://localhost:9000") except ClientError as e: print(f"Error generating presigned URL: {e}") return "" storage = StorageClient()