test ferst

This commit is contained in:
jze9
2026-05-13 19:05:07 +05:00
commit 0ac1c8a862
33 changed files with 3702 additions and 0 deletions

138
api/route/admin_media.py Normal file
View 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}