feat: Implement image thumbnail generation, storage, and API endpoints for assets, including a regeneration utility.

This commit is contained in:
xds
2026-02-05 20:52:50 +03:00
parent 736e5a8c12
commit 76dd976854
26 changed files with 127 additions and 20 deletions

View File

@@ -21,17 +21,31 @@ router = APIRouter(prefix="/api/assets", tags=["Assets"])
@router.get("/{asset_id}")
async def get_asset(asset_id: str, request: Request,dao: DAO = Depends(get_dao),) -> Response:
logger.debug(f"get_asset called for ID: {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"
}
return Response(content=asset.data, media_type="image/png", headers=headers)
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("")
@@ -41,7 +55,13 @@ async def get_assets(request: Request, dao: DAO = Depends(get_dao), limit: int =
# assets = await dao.assets.get_assets() # This line seemed redundant/conflicting in original code
total_count = await dao.assets.get_asset_count()
return AssetsResponse(assets=assets, total_count=total_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)
@@ -62,11 +82,16 @@ async def upload_asset(
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)
@@ -79,4 +104,39 @@ async def upload_asset(
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}