This commit is contained in:
jze9
2026-05-15 20:12:44 +05:00
parent 2335497226
commit dd7d8a7ccf
21 changed files with 672 additions and 121 deletions

View File

@@ -7,9 +7,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from minio import Minio
from minio.error import S3Error
from slugify import slugify
from bd.database import get_db
from bd.models import Media
from bd.models import Media, Article
from route.deps import require_admin
router = APIRouter()
@@ -50,6 +51,7 @@ def ensure_bucket(client: Minio):
@router.post("/upload")
async def upload_media(
file: UploadFile = File(...),
article_id: str | None = Query(None),
db: AsyncSession = Depends(get_db),
_: str = Depends(require_admin),
):
@@ -63,7 +65,27 @@ async def upload_media(
media_type = "image" if content_type in ALLOWED_IMAGE else "video"
ext = (file.filename or "file").rsplit(".", 1)[-1].lower()
object_name = f"{media_type}s/{uuid.uuid4().hex}.{ext}"
# Определяем путь в MinIO
art_uuid = None
if article_id:
try:
art_uuid = uuid.UUID(article_id)
# Проверяем существование статьи
art = (await db.execute(select(Article).where(Article.id == art_uuid))).scalar_one_or_none()
if not art:
art_uuid = None
except ValueError:
art_uuid = None
if art_uuid:
folder = "images" if media_type == "image" else "videos"
art_slug = slugify(art.title) if art and art.title else str(art_uuid)[:8]
art_dir = f"{art_slug}-{str(art_uuid)[:8]}"
base_name = f"{art_slug}-{uuid.uuid4().hex[:6]}.{ext}"
object_name = f"articles/{art_dir}/{folder}/{base_name}"
else:
object_name = f"{media_type}s/{uuid.uuid4().hex}.{ext}"
client = get_minio()
ensure_bucket(client)
@@ -78,10 +100,11 @@ async def upload_media(
public_url = f"{MINIO_PUBLIC_URL}/{MINIO_BUCKET}/{object_name}"
media = Media(
filename=file.filename or object_name,
filename=object_name.split("/")[-1],
url=public_url,
media_type=media_type,
size_bytes=len(data),
article_id=art_uuid,
)
db.add(media)
await db.commit()
@@ -101,20 +124,40 @@ async def list_media(
offset: int = Query(0, ge=0),
limit: int = Query(40, ge=1, le=200),
media_type: str | None = Query(None),
q: str | None = Query(None, max_length=200),
db: AsyncSession = Depends(get_db),
_: str = Depends(require_admin),
):
from sqlalchemy import func
from sqlalchemy.orm import outerjoin
base = select(Media)
if media_type:
base = base.where(Media.media_type == media_type)
if q:
base = base.where(Media.filename.ilike(f"%{q}%"))
total = (await db.execute(select(func.count()).select_from(base.subquery()))).scalar_one()
items = (await db.execute(base.order_by(Media.created_at.desc()).offset(offset).limit(limit))).scalars().all()
# Fetch article titles for media that have an article_id
art_ids = {m.article_id for m in items if m.article_id}
art_titles: dict = {}
if art_ids:
arts = (await db.execute(select(Article.id, Article.title).where(Article.id.in_(art_ids)))).all()
art_titles = {a.id: a.title for a in arts}
return {
"total": total,
"items": [
{"id": str(m.id), "url": m.url, "filename": m.filename, "media_type": m.media_type,
"size_bytes": m.size_bytes, "created_at": m.created_at.isoformat()}
{
"id": str(m.id),
"url": m.url,
"filename": m.filename,
"media_type": m.media_type,
"size_bytes": m.size_bytes,
"created_at": m.created_at.isoformat(),
"article_id": str(m.article_id) if m.article_id else None,
"article_title": art_titles.get(m.article_id) if m.article_id else None,
}
for m in items
],
}