feat: add Printify webhook endpoint for real-time product updates

- Add /webhooks/printify endpoint with HMAC-SHA256 signature verification
- Add Webhooks context to handle product:updated, product:deleted events
- Add ProductDeleteWorker for async product deletion
- Add webhook API methods to Printify client (create, list, delete)
- Add register_webhooks/2 to Printify provider
- Add mix register_webhooks task for one-time webhook registration
- Cache raw request body in endpoint for signature verification

Usage:
1. Generate webhook secret: openssl rand -hex 20
2. Add to provider connection config as "webhook_secret"
3. Register with Printify: mix register_webhooks https://yourshop.com/webhooks/printify

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-01-31 22:41:15 +00:00
parent a2157177b8
commit a9c15ea6ae
13 changed files with 596 additions and 4 deletions

View File

@@ -0,0 +1,41 @@
defmodule SimpleshopTheme.Webhooks.ProductDeleteWorker do
@moduledoc """
Oban worker for deleting products removed from POD providers.
"""
use Oban.Worker, queue: :sync, max_attempts: 3
alias SimpleshopTheme.Products
require Logger
@impl Oban.Worker
def perform(%Oban.Job{args: %{"provider_product_id" => provider_product_id}}) do
case Products.get_provider_connection_by_type("printify") do
nil ->
Logger.warning("No Printify connection found for product deletion")
{:cancel, :no_connection}
conn ->
case Products.get_product_by_provider(conn.id, provider_product_id) do
nil ->
Logger.info("Product #{provider_product_id} already deleted or not found")
:ok
product ->
Logger.info("Deleting product #{product.id} (provider: #{provider_product_id})")
case Products.delete_product(product) do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end
end
end
def enqueue(provider_product_id) do
%{provider_product_id: to_string(provider_product_id)}
|> new()
|> Oban.insert()
end
end