diff --git a/backend/app/domains/media/router.py b/backend/app/domains/media/router.py index 106cba9..799fbe8 100644 --- a/backend/app/domains/media/router.py +++ b/backend/app/domains/media/router.py @@ -5,16 +5,60 @@ import zipfile from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import StreamingResponse +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession 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 import service 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], include_in_schema=False) async def list_assets( @@ -40,6 +84,7 @@ async def list_assets( for a in assets: a.download_url = service.get_download_url(a) a.thumbnail_url = service.get_thumbnail_url(a) + await _resolve_thumbnails_bulk(db, 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") asset.download_url = service.get_download_url(asset) asset.thumbnail_url = service.get_thumbnail_url(asset) + await _resolve_thumbnails_bulk(db, [asset]) return asset