feat(E): add MediaAsset catalog — model, CRUD API, MediaBrowser UI
Migration 040: media_assets table with RLS (tenant_isolation + admin_bypass). domains/media/: MediaAsset model, schemas, service, router with ZIP-download. publish_asset Celery task in rendering/tasks.py. core/storage.py: download_bytes() method for MinIO + LocalStorage. frontend: MediaBrowser.tsx (grid/list, multi-select, zip-download, pagination) + api/media.ts. Route /media (AdminRoute) + sidebar link with Image icon for admin+pm. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ Environment variables (set in docker-compose.yml):
|
||||
|
||||
If MINIO_URL is not set, falls back to LocalStorage (reads/writes to UPLOAD_DIR).
|
||||
"""
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -91,6 +92,12 @@ class MinIOStorage:
|
||||
ExpiresIn=expires_in,
|
||||
)
|
||||
|
||||
def download_bytes(self, object_key: str) -> bytes:
|
||||
"""Download file from MinIO and return as bytes."""
|
||||
buf = io.BytesIO()
|
||||
self._client.download_fileobj(self._bucket, object_key, buf)
|
||||
return buf.getvalue()
|
||||
|
||||
@property
|
||||
def backend(self) -> str:
|
||||
return "minio"
|
||||
@@ -138,6 +145,11 @@ class LocalStorage:
|
||||
def get_url(self, object_key: str, expires_in: int = 3600) -> str:
|
||||
return f"/api/files/{object_key}"
|
||||
|
||||
def download_bytes(self, object_key: str) -> bytes:
|
||||
"""Read file from local storage and return as bytes."""
|
||||
path = self._resolve(object_key)
|
||||
return path.read_bytes()
|
||||
|
||||
@property
|
||||
def backend(self) -> str:
|
||||
return "local"
|
||||
|
||||
Reference in New Issue
Block a user