chat/apps/chat/services/storage.py
Ali Asadi d85be31e77 switch chat media messages to client-side MinIO uploads
Frontend now uploads media directly to MinIO and sends only the
resulting object_key; the backend never touches file bytes. Mattermost
post bodies carry "<type>:<object_key>" instead of a permanent public
URL, and download links are signed fresh on every read (send + list)
so they can't outlive their expiry. Also wires up MINIO_SECURE, which
settings.py previously never read from env.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 12:12:31 +03:30

45 lines
1.7 KiB
Python

from datetime import timedelta
from django.conf import settings
from minio import Minio
class StorageService:
"""
Thin adapter around MinIO for the object-key-in-message-body flow: the
frontend uploads media directly to MinIO and only ever sends us the
resulting object key, so this service's job is limited to turning that
key back into a short-lived download URL on read.
"""
def __init__(self):
endpoint = getattr(settings, "MINIO_ENDPOINT", None)
if not endpoint:
raise ValueError("MINIO_ENDPOINT is not configured")
secure = getattr(settings, "MINIO_SECURE", False)
self._client = Minio(
endpoint,
access_key=getattr(settings, "MINIO_ACCESS_KEY", None),
secret_key=getattr(settings, "MINIO_SECRET_KEY", None),
secure=secure,
)
self._bucket = getattr(settings, "MINIO_BUCKET_CHAT", "chat")
self._expiry = timedelta(
seconds=getattr(settings, "MINIO_PRESIGN_EXPIRY_SECONDS", 3600)
)
def get_download_url(self, object_key: str) -> str | None:
"""
Sign a time-limited download URL for an object the client already
uploaded. Generated fresh on every call rather than cached/stored,
since a stored link would eventually expire while still displayed.
Returns None on failure so one bad key can't fail an entire message
list — callers should treat that as "link unavailable", not an error.
"""
try:
return self._client.presigned_get_object(
self._bucket, object_key, expires=self._expiry
)
except Exception:
return None