"""MinIO / S3-compatible object storage. Provides upload and presigned URL generation for converted DOCX files. """ from __future__ import annotations import boto3 import structlog from botocore.exceptions import ClientError from config import settings from core.exceptions import StorageError log = structlog.get_logger() # Pack credit values — used by payment service to resolve pack → credit count PACK_CREDITS: dict[str, int] = { 'starter': 5, 'studio': 20, 'agency': 60, } def _s3_client(): return boto3.client( 's3', endpoint_url=settings.s3_endpoint_url, region_name=settings.s3_region, aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key, ) def upload_docx(object_key: str, docx_bytes: bytes) -> str: """Upload a DOCX file to MinIO and return a presigned download URL. Args: object_key: S3 object key, e.g. 'conversions/.docx' docx_bytes: Raw DOCX bytes. Returns: Presigned URL valid for settings.s3_presign_expiry_seconds. Raises: StorageError: If upload or presign fails. """ client = _s3_client() try: client.put_object( Bucket=settings.s3_bucket, Key=object_key, Body=docx_bytes, ContentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document', ) log.info('docx_uploaded', key=object_key, size_bytes=len(docx_bytes)) except ClientError as e: log.error('docx_upload_failed', key=object_key, error=str(e)) raise StorageError(f'Upload failed: {e}') try: url = client.generate_presigned_url( 'get_object', Params={'Bucket': settings.s3_bucket, 'Key': object_key}, ExpiresIn=settings.s3_presign_expiry_seconds, ) return url except ClientError as e: log.error('docx_presign_failed', key=object_key, error=str(e)) raise StorageError(f'Presign failed: {e}') def storage_available() -> bool: """Return True if MinIO/S3 is reachable and the bucket exists.""" try: client = _s3_client() client.head_bucket(Bucket=settings.s3_bucket) return True except Exception: return False