142 lines
5.2 KiB
Python
142 lines
5.2 KiB
Python
from typing import List, Optional
|
|
|
|
from aiogram.types import BufferedInputFile
|
|
from fastapi import APIRouter, UploadFile, File, Form, Depends
|
|
from fastapi.openapi.models import MediaType
|
|
from starlette import status
|
|
from starlette.exceptions import HTTPException
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response, JSONResponse
|
|
|
|
from api.models.AssetDTO import AssetsResponse, AssetResponse
|
|
from models.Asset import Asset, AssetType
|
|
from repos.dao import DAO
|
|
from api.dependency import get_dao
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/assets", tags=["Assets"])
|
|
|
|
|
|
@router.get("/{asset_id}")
|
|
async def get_asset(
|
|
asset_id: str,
|
|
request: Request,
|
|
thumbnail: bool = False,
|
|
dao: DAO = Depends(get_dao)
|
|
) -> Response:
|
|
logger.debug(f"get_asset called for ID: {asset_id}, thumbnail={thumbnail}")
|
|
asset = await dao.assets.get_asset(asset_id)
|
|
# 2. Проверка на существование
|
|
if not asset:
|
|
raise HTTPException(status_code=404, detail="Asset not found")
|
|
|
|
headers = {
|
|
# Кэшировать на 1 год (31536000 сек)
|
|
"Cache-Control": "public, max-age=31536000, immutable"
|
|
}
|
|
|
|
content = asset.data
|
|
media_type = "image/png" # Default, or detect
|
|
|
|
if thumbnail and asset.thumbnail:
|
|
content = asset.thumbnail
|
|
media_type = "image/jpeg"
|
|
|
|
return Response(content=content, media_type=media_type, headers=headers)
|
|
|
|
|
|
@router.get("")
|
|
async def get_assets(request: Request, dao: DAO = Depends(get_dao), limit: int = 10, offset: int = 0) -> AssetsResponse:
|
|
logger.info(f"get_assets called. Limit: {limit}, Offset: {offset}")
|
|
assets = await dao.assets.get_assets(limit, offset)
|
|
# assets = await dao.assets.get_assets() # This line seemed redundant/conflicting in original code
|
|
total_count = await dao.assets.get_asset_count()
|
|
|
|
# Manually map to DTO to trigger computed fields validation if necessary,
|
|
# but primarily to ensure valid Pydantic models for the response list.
|
|
# Asset.model_dump() generally includes computed fields (url) if configured.
|
|
# Let's ensure strict conversion.
|
|
asset_responses = [AssetResponse.model_validate(a.model_dump()) for a in assets]
|
|
|
|
return AssetsResponse(assets=asset_responses, total_count=total_count)
|
|
|
|
|
|
|
|
@router.post("/upload", response_model=AssetResponse, status_code=status.HTTP_201_CREATED)
|
|
async def upload_asset(
|
|
file: UploadFile = File(...),
|
|
linked_char_id: Optional[str] = Form(None),
|
|
dao: DAO = Depends(get_dao),
|
|
):
|
|
logger.info(f"upload_asset called. Filename: {file.filename}, ContentType: {file.content_type}, LinkedCharId: {linked_char_id}")
|
|
if not file.content_type:
|
|
raise HTTPException(status_code=400, detail="Unknown file type")
|
|
|
|
if not file.content_type.startswith("image/"):
|
|
raise HTTPException(status_code=400, detail=f"Unsupported content type: {file.content_type}")
|
|
|
|
data = await file.read()
|
|
if not data:
|
|
raise HTTPException(status_code=400, detail="Empty file")
|
|
|
|
# Generate thumbnail
|
|
from utils.image_utils import create_thumbnail
|
|
thumbnail_bytes = await asyncio.to_thread(create_thumbnail, data)
|
|
|
|
asset = Asset(
|
|
name=file.filename or "upload",
|
|
type=AssetType.IMAGE,
|
|
linked_char_id=linked_char_id,
|
|
data=data,
|
|
thumbnail=thumbnail_bytes
|
|
)
|
|
|
|
asset_id = await dao.assets.create_asset(asset)
|
|
asset.id = str(asset_id)
|
|
logger.info(f"Asset created successfully. ID: {asset_id}")
|
|
|
|
return AssetResponse(
|
|
id=asset.id,
|
|
name=asset.name,
|
|
type=asset.type.value if hasattr(asset.type, "value") else asset.type,
|
|
linked_char_id=asset.linked_char_id,
|
|
created_at=asset.created_at,
|
|
url=asset.url
|
|
)
|
|
|
|
|
|
@router.post("/regenerate_thumbnails")
|
|
async def regenerate_thumbnails(dao: DAO = Depends(get_dao)):
|
|
"""
|
|
Regenerates thumbnails for all existing image assets that don't have one.
|
|
"""
|
|
logger.info("Starting thumbnail regeneration task")
|
|
from utils.image_utils import create_thumbnail
|
|
import asyncio
|
|
|
|
# Get all assets (pagination loop might be needed for huge datasets, but simple list for now)
|
|
# We'll rely on DAO providing a method or just fetch large chunk.
|
|
# Assuming get_assets might have limit, let's fetch in chunks or just all if possible within limit.
|
|
# Ideally should use a specific repo method for iteration.
|
|
# For now, let's fetch first 1000 or similar.
|
|
assets = await dao.assets.get_assets(limit=1000, offset=0, with_data=True)
|
|
logger.info(f"Found {len(assets)} assets")
|
|
count = 0
|
|
updated = 0
|
|
|
|
for asset in assets:
|
|
if asset.type == AssetType.IMAGE and asset.data :
|
|
try:
|
|
thumb = await asyncio.to_thread(create_thumbnail, asset.data)
|
|
if thumb:
|
|
asset.thumbnail = thumb
|
|
await dao.assets.update_asset(asset.id, asset)
|
|
updated += 1
|
|
except Exception as e:
|
|
logger.error(f"Failed to regenerate thumbnail for asset {asset.id}: {e}")
|
|
count += 1
|
|
|
|
return {"status": "completed", "processed": count, "updated": updated} |