Files
ai-char-bot/api/endpoints/assets_router.py
2026-02-05 22:51:36 +03:00

143 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 asyncio
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}