import os import logging import boto3 from botocore.client import Config from botocore.exceptions import ClientError import json logger = logging.getLogger("storage") # Read settings from environment MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "minio:9000") MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minio_admin") MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minio_secure_password_2026") MINIO_BUCKET = os.getenv("MINIO_BUCKET", "pragativeer-uploads") # Initialize S3/Minio client # Since backend runs inside Docker, endpoint_url must point to 'http://minio:9000' # However, from the client browser, it will be reached via 'http://localhost:9000' s3_client = boto3.client( "s3", endpoint_url=f"http://{MINIO_ENDPOINT}", aws_access_key_id=MINIO_ACCESS_KEY, aws_secret_access_key=MINIO_SECRET_KEY, config=Config(signature_version="s3v4"), region_name="us-east-1" ) def ensure_bucket_exists(): try: # Check if bucket exists s3_client.head_bucket(Bucket=MINIO_BUCKET) logger.info(f"Minio bucket '{MINIO_BUCKET}' already exists.") except ClientError as e: error_code = e.response["Error"]["Code"] if error_code == "404": try: # Create bucket s3_client.create_bucket(Bucket=MINIO_BUCKET) logger.info(f"Successfully created Minio bucket '{MINIO_BUCKET}'.") # Set public read policy for direct browser access bucket_policy = { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": [f"arn:aws:s3:::{MINIO_BUCKET}/*"] } ] } s3_client.put_bucket_policy( Bucket=MINIO_BUCKET, Policy=json.dumps(bucket_policy) ) logger.info(f"Public read policy applied to Minio bucket '{MINIO_BUCKET}'.") except Exception as ex: logger.error(f"Failed to create Minio bucket: {ex}") else: logger.error(f"Error checking Minio bucket: {e}") def upload_file(file_content: bytes, object_name: str, content_type: str = "application/octet-stream") -> str: """Uploads file content to Minio and returns the direct public URL.""" try: ensure_bucket_exists() s3_client.put_object( Bucket=MINIO_BUCKET, Key=object_name, Body=file_content, ContentType=content_type ) # Construct public URL (from browser's point of view, it connects to localhost:9000) # We can resolve this dynamically by returning a path like /api/uploads/ or direct localhost:9000 link public_url = f"/api/storage/{MINIO_BUCKET}/{object_name}" logger.info(f"Successfully uploaded {object_name} to Minio. URL: {public_url}") return public_url except Exception as e: logger.error(f"Failed to upload file to Minio: {e}") return "" def get_file(object_name: str) -> bytes: """Downloads file content from Minio.""" try: response = s3_client.get_object(Bucket=MINIO_BUCKET, Key=object_name) return response["Body"].read() except Exception as e: logger.error(f"Failed to get file from Minio: {e}") return b""