feat(media): resolve thumbnail_url using product thumbnail priority (latest still → cad thumbnail)

This commit is contained in:
2026-03-07 00:23:30 +01:00
parent c7d74ec636
commit 10ed1b5e91
+47 -1
View File
@@ -5,16 +5,60 @@ import zipfile
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db from app.database import get_db
from app.domains.media.models import MediaAssetType from app.domains.media.models import MediaAsset, MediaAssetType
from app.domains.media.schemas import MediaAssetOut from app.domains.media.schemas import MediaAssetOut
from app.domains.media import service from app.domains.media import service
router = APIRouter(prefix="/api/media", tags=["media"], redirect_slashes=False) router = APIRouter(prefix="/api/media", tags=["media"], redirect_slashes=False)
async def _resolve_thumbnails_bulk(db: AsyncSession, assets: list) -> None:
"""Resolve thumbnail_url for assets using the same priority as product pages.
Priority per asset (applied only when thumbnail_url is not yet set):
1. Latest 'still' MediaAsset for the same product (rendered preview)
2. Product's linked CadFile thumbnail (/api/cad/{id}/thumbnail)
"""
needs = [a for a in assets if not a.thumbnail_url and a.product_id]
if not needs:
return
product_ids = list({a.product_id for a in needs})
# 1. Latest 'still' asset per product (DISTINCT ON product_id ORDER BY created_at DESC)
still_rows = await db.execute(
select(MediaAsset.product_id, MediaAsset.id)
.where(
MediaAsset.product_id.in_(product_ids),
MediaAsset.asset_type == MediaAssetType.still,
MediaAsset.is_archived == False, # noqa: E712
)
.order_by(MediaAsset.product_id, MediaAsset.created_at.desc())
.distinct(MediaAsset.product_id)
)
best_still: dict[str, str] = {str(pid): str(sid) for pid, sid in still_rows.all()}
# 2. Fallback: product's cad_file_id → CAD thumbnail endpoint
from app.domains.products.models import Product
prod_rows = await db.execute(
select(Product.id, Product.cad_file_id).where(Product.id.in_(product_ids))
)
product_cad: dict[str, str] = {
str(pid): str(cid) for pid, cid in prod_rows.all() if cid
}
for a in needs:
pid = str(a.product_id)
if pid in best_still:
a.thumbnail_url = f"/api/media/{best_still[pid]}/download"
elif pid in product_cad:
a.thumbnail_url = f"/api/cad/{product_cad[pid]}/thumbnail"
@router.get("", response_model=list[MediaAssetOut]) @router.get("", response_model=list[MediaAssetOut])
@router.get("/", response_model=list[MediaAssetOut], include_in_schema=False) @router.get("/", response_model=list[MediaAssetOut], include_in_schema=False)
async def list_assets( async def list_assets(
@@ -40,6 +84,7 @@ async def list_assets(
for a in assets: for a in assets:
a.download_url = service.get_download_url(a) a.download_url = service.get_download_url(a)
a.thumbnail_url = service.get_thumbnail_url(a) a.thumbnail_url = service.get_thumbnail_url(a)
await _resolve_thumbnails_bulk(db, assets)
return assets return assets
@@ -50,6 +95,7 @@ async def get_asset(asset_id: uuid.UUID, db: AsyncSession = Depends(get_db)):
raise HTTPException(404, "Asset not found") raise HTTPException(404, "Asset not found")
asset.download_url = service.get_download_url(asset) asset.download_url = service.get_download_url(asset)
asset.thumbnail_url = service.get_thumbnail_url(asset) asset.thumbnail_url = service.get_thumbnail_url(asset)
await _resolve_thumbnails_bulk(db, [asset])
return asset return asset