feat: add product image download pipeline for PageSpeed 100%

Downloads Printify CDN images via ImageDownloadWorker, processes
through Media pipeline (WebP conversion, AVIF/WebP variant generation),
and links to ProductImage via new image_id FK.

- Add image_id to product_images table
- ImageDownloadWorker downloads and processes external images
- sync_product_images preserves image_id when URL unchanged
- PreviewData uses local images for responsive <picture> elements
- VariantCache enqueues pending downloads on startup
- mix simpleshop.download_images backfill task

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-01 00:26:19 +00:00
parent c818d0399c
commit 1b49b470f2
12 changed files with 381 additions and 33 deletions

View File

@@ -185,7 +185,11 @@ defmodule SimpleshopTheme.Theme.PreviewData do
end
defp get_real_products do
Products.list_products(visible: true, status: "active", preload: [:images, :variants])
Products.list_products(
visible: true,
status: "active",
preload: [images: :image, variants: []]
)
|> Enum.map(&product_to_map/1)
end
@@ -228,16 +232,22 @@ defmodule SimpleshopTheme.Theme.PreviewData do
in_stock = Enum.any?(available_variants)
on_sale = Enum.any?(product.variants, &SimpleshopTheme.Products.ProductVariant.on_sale?/1)
# Use local image if available, fall back to CDN URL
{image_url, image_id, source_width} = image_attrs(first_image)
{hover_image_url, hover_image_id, hover_source_width} = image_attrs(second_image)
%{
id: product.slug,
name: product.title,
description: product.description,
price: if(cheapest_variant, do: cheapest_variant.price, else: 0),
compare_at_price: if(cheapest_variant, do: cheapest_variant.compare_at_price, else: nil),
image_url: if(first_image, do: first_image.src, else: nil),
hover_image_url: if(second_image, do: second_image.src, else: nil),
source_width: nil,
hover_source_width: nil,
image_url: image_url,
image_id: image_id,
hover_image_url: hover_image_url,
hover_image_id: hover_image_id,
source_width: source_width,
hover_source_width: hover_source_width,
category: product.category,
slug: product.slug,
in_stock: in_stock,
@@ -246,6 +256,20 @@ defmodule SimpleshopTheme.Theme.PreviewData do
}
end
# Extract image attributes, preferring local Media.Image when available
defp image_attrs(nil), do: {nil, nil, nil}
defp image_attrs(%{image_id: image_id, image: %{source_width: source_width}})
when not is_nil(image_id) do
# Local image available - use image_id for responsive <picture> element
{nil, image_id, source_width}
end
defp image_attrs(%{src: src}) do
# Fall back to CDN URL
{src, nil, nil}
end
# Default source width for mockup variants (max generated size)
@mockup_source_width 1200