feat: add admin provider setup UI with improved product sync

- Add /admin/providers LiveView for connecting and managing POD providers
- Implement pagination for Printify API (handles all products, not just first page)
- Add parallel processing (5 concurrent) for faster product sync
- Add slug-based fallback matching when provider_product_id changes
- Add error recovery with try/rescue to prevent stuck sync status
- Add checksum-based change detection to skip unchanged products
- Add upsert tests covering race conditions and slug matching
- Add Printify provider tests
- Document Printify integration research (product identity, order risks,
  open source vs managed hosting implications)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-01-31 22:08:34 +00:00
parent bbd748f123
commit 5b736b99fd
17 changed files with 1352 additions and 38 deletions

View File

@@ -41,11 +41,7 @@ defmodule SimpleshopTheme.Providers.Printify do
else
with api_key when is_binary(api_key) <- ProviderConnection.get_api_key(conn),
:ok <- set_api_key(api_key),
{:ok, response} <- Client.list_products(shop_id) do
products =
response["data"]
|> Enum.map(&normalize_product/1)
{:ok, products} <- fetch_all_products(shop_id) do
{:ok, products}
else
nil -> {:error, :no_api_key}
@@ -54,6 +50,33 @@ defmodule SimpleshopTheme.Providers.Printify do
end
end
# Fetches all products by paginating through the API
defp fetch_all_products(shop_id) do
fetch_products_page(shop_id, 1, [])
end
defp fetch_products_page(shop_id, page, acc) do
case Client.list_products(shop_id, page: page) do
{:ok, response} ->
products = Enum.map(response["data"] || [], &normalize_product/1)
all_products = acc ++ products
current_page = response["current_page"] || page
last_page = response["last_page"] || 1
if current_page < last_page do
# Small delay to be nice to rate limits (600/min = 10/sec)
Process.sleep(100)
fetch_products_page(shop_id, page + 1, all_products)
else
{:ok, all_products}
end
{:error, _} = error ->
error
end
end
@impl true
def submit_order(%ProviderConnection{config: config} = conn, order) do
shop_id = config["shop_id"]