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}