feat(backend): add FastAPI backend — procurement, inventory domain, Casdoor auth, PM arrival endpoints
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""MinIO client — S3-compatible object storage for invoice / proforma attachments.
|
||||
|
||||
The `minio` SDK is synchronous; every call is wrapped with
|
||||
``asyncio.to_thread`` so the event loop is never blocked.
|
||||
"""
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
|
||||
from minio import Minio
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_client() -> Minio:
|
||||
"""Lazily built Minio client from the PROCUREMENT_MINIO_* settings."""
|
||||
return Minio(
|
||||
settings.MINIO_ENDPOINT,
|
||||
access_key=settings.MINIO_ACCESS_KEY,
|
||||
secret_key=settings.MINIO_SECRET_KEY,
|
||||
secure=settings.MINIO_SECURE,
|
||||
)
|
||||
|
||||
|
||||
async def ensure_bucket() -> None:
|
||||
"""Create the attachments bucket if it does not exist (idempotent)."""
|
||||
client = get_client()
|
||||
try:
|
||||
exists = await asyncio.to_thread(client.bucket_exists, settings.MINIO_BUCKET)
|
||||
if not exists:
|
||||
await asyncio.to_thread(client.make_bucket, settings.MINIO_BUCKET)
|
||||
logger.info("Created MinIO bucket %r", settings.MINIO_BUCKET)
|
||||
except Exception:
|
||||
logger.exception("MinIO bucket check failed — is the minio service reachable?")
|
||||
|
||||
|
||||
async def put_object(key: str, content: bytes, mime_type: str) -> None:
|
||||
"""Upload a byte payload to the bucket (overwrites any existing object)."""
|
||||
await asyncio.to_thread(
|
||||
get_client().put_object,
|
||||
settings.MINIO_BUCKET,
|
||||
key,
|
||||
io.BytesIO(content),
|
||||
length=len(content),
|
||||
content_type=mime_type,
|
||||
)
|
||||
|
||||
|
||||
async def get_object_bytes(key: str) -> bytes:
|
||||
"""Download an object's bytes from the bucket."""
|
||||
response = await asyncio.to_thread(get_client().get_object, settings.MINIO_BUCKET, key)
|
||||
try:
|
||||
return response.read()
|
||||
finally:
|
||||
response.close()
|
||||
response.release_conn()
|
||||
|
||||
|
||||
async def remove_object(key: str) -> None:
|
||||
"""Delete an object; missing objects are ignored (best-effort)."""
|
||||
try:
|
||||
await asyncio.to_thread(get_client().remove_object, settings.MINIO_BUCKET, key)
|
||||
except Exception:
|
||||
logger.warning("Could not remove MinIO object %r — ignoring", key)
|
||||
Reference in New Issue
Block a user