Add initial project configuration and setup for Reference Board Viewer application. Include EditorConfig for consistent coding styles, pre-commit hooks for linting and formatting, Docker Compose for local development with PostgreSQL and MinIO, and a Nix flake for development environment management. Establish CI/CD pipeline for automated testing and deployment.
This commit is contained in:
119
backend/app/core/storage.py
Normal file
119
backend/app/core/storage.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""MinIO storage client utilities."""
|
||||
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from typing import BinaryIO
|
||||
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StorageClient:
|
||||
"""MinIO storage client wrapper."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize MinIO client."""
|
||||
self.client = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=f"{'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,
|
||||
config=Config(signature_version="s3v4"),
|
||||
)
|
||||
self.bucket = settings.MINIO_BUCKET
|
||||
self._ensure_bucket_exists()
|
||||
|
||||
def _ensure_bucket_exists(self) -> None:
|
||||
"""Create bucket if it doesn't exist."""
|
||||
try:
|
||||
self.client.head_bucket(Bucket=self.bucket)
|
||||
except ClientError:
|
||||
logger.info(f"Creating bucket: {self.bucket}")
|
||||
self.client.create_bucket(Bucket=self.bucket)
|
||||
|
||||
def upload_file(self, file_data: BinaryIO, object_name: str, content_type: str) -> str:
|
||||
"""Upload file to MinIO.
|
||||
|
||||
Args:
|
||||
file_data: File data to upload
|
||||
object_name: S3 object name (path)
|
||||
content_type: MIME type of the file
|
||||
|
||||
Returns:
|
||||
str: Object URL
|
||||
|
||||
Raises:
|
||||
Exception: If upload fails
|
||||
"""
|
||||
try:
|
||||
self.client.upload_fileobj(
|
||||
file_data,
|
||||
self.bucket,
|
||||
object_name,
|
||||
ExtraArgs={"ContentType": content_type},
|
||||
)
|
||||
return f"{settings.MINIO_ENDPOINT}/{self.bucket}/{object_name}"
|
||||
except ClientError as e:
|
||||
logger.error(f"Failed to upload file {object_name}: {e}")
|
||||
raise
|
||||
|
||||
def download_file(self, object_name: str) -> BytesIO:
|
||||
"""Download file from MinIO.
|
||||
|
||||
Args:
|
||||
object_name: S3 object name (path)
|
||||
|
||||
Returns:
|
||||
BytesIO: File data
|
||||
|
||||
Raises:
|
||||
Exception: If download fails
|
||||
"""
|
||||
try:
|
||||
file_data = BytesIO()
|
||||
self.client.download_fileobj(self.bucket, object_name, file_data)
|
||||
file_data.seek(0)
|
||||
return file_data
|
||||
except ClientError as e:
|
||||
logger.error(f"Failed to download file {object_name}: {e}")
|
||||
raise
|
||||
|
||||
def delete_file(self, object_name: str) -> None:
|
||||
"""Delete file from MinIO.
|
||||
|
||||
Args:
|
||||
object_name: S3 object name (path)
|
||||
|
||||
Raises:
|
||||
Exception: If deletion fails
|
||||
"""
|
||||
try:
|
||||
self.client.delete_object(Bucket=self.bucket, Key=object_name)
|
||||
except ClientError as e:
|
||||
logger.error(f"Failed to delete file {object_name}: {e}")
|
||||
raise
|
||||
|
||||
def file_exists(self, object_name: str) -> bool:
|
||||
"""Check if file exists in MinIO.
|
||||
|
||||
Args:
|
||||
object_name: S3 object name (path)
|
||||
|
||||
Returns:
|
||||
bool: True if file exists, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.client.head_object(Bucket=self.bucket, Key=object_name)
|
||||
return True
|
||||
except ClientError:
|
||||
return False
|
||||
|
||||
|
||||
# Global storage client instance
|
||||
storage_client = StorageClient()
|
||||
|
||||
Reference in New Issue
Block a user