Modello notifiche + Servizio storage MinIO
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import String, Boolean, DateTime, DECIMAL, Integer, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Notification(Base):
|
||||
__tablename__ = "notifications"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
body: Mapped[str | None] = mapped_column(Text)
|
||||
link: Mapped[str | None] = mapped_column(String(500))
|
||||
is_read: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
|
||||
class AlertConfig(Base):
|
||||
__tablename__ = "alert_config"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
|
||||
value: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(String(500))
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc),
|
||||
)
|
||||
@@ -0,0 +1,109 @@
|
||||
import io
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import structlog
|
||||
from minio import Minio
|
||||
from minio.error import S3Error
|
||||
from fastapi import UploadFile, HTTPException, status
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
_client: Minio | None = None
|
||||
_public_client: Minio | None = None
|
||||
|
||||
|
||||
def get_client() -> Minio:
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = Minio(
|
||||
settings.minio_endpoint,
|
||||
access_key=settings.minio_access_key,
|
||||
secret_key=settings.minio_secret_key,
|
||||
secure=settings.minio_secure,
|
||||
)
|
||||
return _client
|
||||
|
||||
|
||||
def _get_public_client() -> Minio:
|
||||
global _public_client
|
||||
if _public_client is None:
|
||||
parsed = urlparse(settings.minio_public_url)
|
||||
endpoint = parsed.netloc
|
||||
secure = parsed.scheme == "https"
|
||||
_public_client = Minio(
|
||||
endpoint,
|
||||
access_key=settings.minio_access_key,
|
||||
secret_key=settings.minio_secret_key,
|
||||
secure=secure,
|
||||
region="us-east-1",
|
||||
)
|
||||
return _public_client
|
||||
|
||||
|
||||
def _ensure_bucket(bucket: str) -> None:
|
||||
client = get_client()
|
||||
try:
|
||||
if not client.bucket_exists(bucket):
|
||||
client.make_bucket(bucket)
|
||||
logger.info("minio_bucket_created", bucket=bucket)
|
||||
except S3Error as exc:
|
||||
logger.error("minio_bucket_error", bucket=bucket, error=str(exc))
|
||||
raise
|
||||
|
||||
|
||||
async def upload_photo(file: UploadFile, valuation_id: int) -> str:
|
||||
bucket = settings.minio_bucket_photos
|
||||
try:
|
||||
_ensure_bucket(bucket)
|
||||
except S3Error as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Storage non disponibile",
|
||||
) from exc
|
||||
|
||||
ext = ""
|
||||
if file.filename and "." in file.filename:
|
||||
ext = "." + file.filename.rsplit(".", 1)[-1].lower()
|
||||
|
||||
object_name = f"valuations/{valuation_id}/{uuid.uuid4().hex}{ext}"
|
||||
|
||||
content = await file.read()
|
||||
content_length = len(content)
|
||||
content_type = file.content_type or "application/octet-stream"
|
||||
|
||||
client = get_client()
|
||||
try:
|
||||
client.put_object(
|
||||
bucket,
|
||||
object_name,
|
||||
io.BytesIO(content),
|
||||
length=content_length,
|
||||
content_type=content_type,
|
||||
)
|
||||
logger.info("minio_upload_ok", object_name=object_name)
|
||||
return object_name
|
||||
except S3Error as exc:
|
||||
logger.error("minio_upload_error", object_name=object_name, error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Errore durante il caricamento del file",
|
||||
) from exc
|
||||
|
||||
|
||||
def get_presigned_url(storage_path: str, expires_hours: int = 4) -> str | None:
|
||||
bucket = settings.minio_bucket_photos
|
||||
client = _get_public_client()
|
||||
try:
|
||||
url = client.presigned_get_object(
|
||||
bucket,
|
||||
storage_path,
|
||||
expires=timedelta(hours=expires_hours),
|
||||
)
|
||||
return url
|
||||
except S3Error as exc:
|
||||
logger.warning("minio_presigned_error", path=storage_path, error=str(exc))
|
||||
return None
|
||||
Reference in New Issue
Block a user