test ferst
This commit is contained in:
138
api/route/admin_media.py
Normal file
138
api/route/admin_media.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
import io
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from minio import Minio
|
||||
from minio.error import S3Error
|
||||
|
||||
from bd.database import get_db
|
||||
from bd.models import Media
|
||||
from route.deps import require_admin
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "minio:9000")
|
||||
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minioadmin")
|
||||
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin")
|
||||
MINIO_BUCKET = os.getenv("MINIO_BUCKET", "news-media")
|
||||
MINIO_PUBLIC_URL = os.getenv("MINIO_PUBLIC_URL", "http://localhost:9000")
|
||||
|
||||
ALLOWED_IMAGE = {"image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"}
|
||||
ALLOWED_VIDEO = {"video/mp4", "video/webm", "video/ogg", "video/quicktime"}
|
||||
MAX_SIZE_BYTES = 100 * 1024 * 1024 # 100 MB
|
||||
|
||||
|
||||
def get_minio() -> Minio:
|
||||
return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)
|
||||
|
||||
|
||||
def ensure_bucket(client: Minio):
|
||||
try:
|
||||
if not client.bucket_exists(MINIO_BUCKET):
|
||||
client.make_bucket(MINIO_BUCKET)
|
||||
policy = f"""{{
|
||||
"Version":"2012-10-17",
|
||||
"Statement":[{{
|
||||
"Effect":"Allow",
|
||||
"Principal":"*",
|
||||
"Action":["s3:GetObject"],
|
||||
"Resource":["arn:aws:s3:::{MINIO_BUCKET}/*"]
|
||||
}}]
|
||||
}}"""
|
||||
client.set_bucket_policy(MINIO_BUCKET, policy)
|
||||
except S3Error:
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_media(
|
||||
file: UploadFile = File(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: str = Depends(require_admin),
|
||||
):
|
||||
content_type = file.content_type or ""
|
||||
if content_type not in ALLOWED_IMAGE and content_type not in ALLOWED_VIDEO:
|
||||
raise HTTPException(status_code=400, detail=f"Тип файла не поддерживается: {content_type}")
|
||||
|
||||
data = await file.read()
|
||||
if len(data) > MAX_SIZE_BYTES:
|
||||
raise HTTPException(status_code=400, detail="Файл слишком большой (макс. 100 МБ)")
|
||||
|
||||
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}"
|
||||
|
||||
client = get_minio()
|
||||
ensure_bucket(client)
|
||||
client.put_object(
|
||||
MINIO_BUCKET,
|
||||
object_name,
|
||||
io.BytesIO(data),
|
||||
length=len(data),
|
||||
content_type=content_type,
|
||||
)
|
||||
|
||||
public_url = f"{MINIO_PUBLIC_URL}/{MINIO_BUCKET}/{object_name}"
|
||||
|
||||
media = Media(
|
||||
filename=file.filename or object_name,
|
||||
url=public_url,
|
||||
media_type=media_type,
|
||||
size_bytes=len(data),
|
||||
)
|
||||
db.add(media)
|
||||
await db.commit()
|
||||
await db.refresh(media)
|
||||
|
||||
return {
|
||||
"id": str(media.id),
|
||||
"url": public_url,
|
||||
"filename": media.filename,
|
||||
"media_type": media_type,
|
||||
"size_bytes": media.size_bytes,
|
||||
}
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_media(
|
||||
offset: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
media_type: str | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: str = Depends(require_admin),
|
||||
):
|
||||
q = select(Media)
|
||||
if media_type:
|
||||
q = q.where(Media.media_type == media_type)
|
||||
items = (await db.execute(q.order_by(Media.created_at.desc()).offset(offset).limit(limit))).scalars().all()
|
||||
return [
|
||||
{"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()}
|
||||
for m in items
|
||||
]
|
||||
|
||||
|
||||
@router.delete("/{media_id}")
|
||||
async def delete_media(
|
||||
media_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: str = Depends(require_admin),
|
||||
):
|
||||
import uuid as uuid_lib
|
||||
media = (await db.execute(select(Media).where(Media.id == uuid_lib.UUID(media_id)))).scalar_one_or_none()
|
||||
if not media:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
|
||||
object_name = media.url.split(f"/{MINIO_BUCKET}/", 1)[-1]
|
||||
try:
|
||||
client = get_minio()
|
||||
client.remove_object(MINIO_BUCKET, object_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await db.delete(media)
|
||||
await db.commit()
|
||||
return {"ok": True}
|
||||
Reference in New Issue
Block a user