Modello notifiche + Servizio storage MinIO

This commit is contained in:
2026-06-18 17:25:31 +02:00
parent 94f40d51cf
commit 7eb2259553
2 changed files with 142 additions and 0 deletions
+33
View File
@@ -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),
)
+109
View File
@@ -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