mix ci alias: compile --warning-as-errors, format --check-formatted, credo, dialyzer, test. Credo configured with sensible defaults. Dialyzer ignore file for false positives (Stripe types, Mix tasks, ExUnit internals). Credo fixes: map_join, filter consolidation, nesting extraction, cond→if simplification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
246 lines
5.9 KiB
Elixir
246 lines
5.9 KiB
Elixir
defmodule SimpleshopTheme.Clients.Printify do
|
|
@moduledoc """
|
|
HTTP client for the Printify API.
|
|
|
|
Handles authentication and provides low-level API access.
|
|
Requires PRINTIFY_API_TOKEN environment variable to be set.
|
|
"""
|
|
|
|
@base_url "https://api.printify.com/v1"
|
|
|
|
@doc """
|
|
Get the API token.
|
|
|
|
Checks process dictionary first (for provider connections with stored credentials),
|
|
then falls back to environment variable (for development/mockup generation).
|
|
"""
|
|
def api_token do
|
|
Process.get(:printify_api_key) ||
|
|
System.get_env("PRINTIFY_API_TOKEN") ||
|
|
raise "PRINTIFY_API_TOKEN environment variable is not set"
|
|
end
|
|
|
|
@doc """
|
|
Make a GET request to the Printify API.
|
|
"""
|
|
def get(path, _opts \\ []) do
|
|
url = @base_url <> path
|
|
|
|
case Req.get(url, headers: auth_headers(), receive_timeout: 30_000) do
|
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
|
{:ok, body}
|
|
|
|
{:ok, %Req.Response{status: status, body: body}} ->
|
|
{:error, {status, body}}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Make a POST request to the Printify API.
|
|
"""
|
|
def post(path, body, _opts \\ []) do
|
|
url = @base_url <> path
|
|
|
|
case Req.post(url, json: body, headers: auth_headers(), receive_timeout: 60_000) do
|
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
|
{:ok, body}
|
|
|
|
{:ok, %Req.Response{status: status, body: body}} ->
|
|
{:error, {status, body}}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Make a DELETE request to the Printify API.
|
|
"""
|
|
def delete(path, _opts \\ []) do
|
|
url = @base_url <> path
|
|
|
|
case Req.delete(url, headers: auth_headers(), receive_timeout: 30_000) do
|
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
|
{:ok, body}
|
|
|
|
{:ok, %Req.Response{status: status}} when status == 204 ->
|
|
{:ok, nil}
|
|
|
|
{:ok, %Req.Response{status: status, body: body}} ->
|
|
{:error, {status, body}}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get all shops for the authenticated account.
|
|
"""
|
|
def get_shops do
|
|
get("/shops.json")
|
|
end
|
|
|
|
@doc """
|
|
Get the first shop ID for the account.
|
|
"""
|
|
def get_shop_id do
|
|
case get_shops() do
|
|
{:ok, shops} when is_list(shops) and shops != [] ->
|
|
{:ok, hd(shops)["id"]}
|
|
|
|
{:ok, []} ->
|
|
{:error, :no_shops}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get all blueprints (product types) from the catalog.
|
|
"""
|
|
def get_blueprints do
|
|
get("/catalog/blueprints.json")
|
|
end
|
|
|
|
@doc """
|
|
Get print providers for a specific blueprint.
|
|
"""
|
|
def get_print_providers(blueprint_id) do
|
|
get("/catalog/blueprints/#{blueprint_id}/print_providers.json")
|
|
end
|
|
|
|
@doc """
|
|
Get variants for a specific blueprint and print provider.
|
|
"""
|
|
def get_variants(blueprint_id, print_provider_id) do
|
|
get("/catalog/blueprints/#{blueprint_id}/print_providers/#{print_provider_id}/variants.json")
|
|
end
|
|
|
|
@doc """
|
|
Get shipping information for a blueprint/provider combination.
|
|
"""
|
|
def get_shipping(blueprint_id, print_provider_id) do
|
|
get("/catalog/blueprints/#{blueprint_id}/print_providers/#{print_provider_id}/shipping.json")
|
|
end
|
|
|
|
@doc """
|
|
Upload an image to Printify via URL.
|
|
"""
|
|
def upload_image(file_name, url) do
|
|
post("/uploads/images.json", %{
|
|
file_name: file_name,
|
|
url: url
|
|
})
|
|
end
|
|
|
|
@doc """
|
|
Create a product in a shop.
|
|
"""
|
|
def create_product(shop_id, product_data) do
|
|
post("/shops/#{shop_id}/products.json", product_data)
|
|
end
|
|
|
|
@doc """
|
|
Get a product by ID.
|
|
"""
|
|
def get_product(shop_id, product_id) do
|
|
get("/shops/#{shop_id}/products/#{product_id}.json")
|
|
end
|
|
|
|
@doc """
|
|
List all products in a shop.
|
|
Printify allows a maximum of 50 products per page.
|
|
"""
|
|
def list_products(shop_id, opts \\ []) do
|
|
limit = Keyword.get(opts, :limit, 50)
|
|
page = Keyword.get(opts, :page, 1)
|
|
get("/shops/#{shop_id}/products.json?limit=#{limit}&page=#{page}")
|
|
end
|
|
|
|
@doc """
|
|
Delete a product from a shop.
|
|
"""
|
|
def delete_product(shop_id, product_id) do
|
|
delete("/shops/#{shop_id}/products/#{product_id}.json")
|
|
end
|
|
|
|
@doc """
|
|
Create an order in a shop.
|
|
"""
|
|
def create_order(shop_id, order_data) do
|
|
post("/shops/#{shop_id}/orders.json", order_data)
|
|
end
|
|
|
|
@doc """
|
|
Get an order by ID.
|
|
"""
|
|
def get_order(shop_id, order_id) do
|
|
get("/shops/#{shop_id}/orders/#{order_id}.json")
|
|
end
|
|
|
|
# =============================================================================
|
|
# Webhooks
|
|
# =============================================================================
|
|
|
|
@doc """
|
|
Register a webhook with Printify.
|
|
|
|
## Event types
|
|
- "product:publish:started"
|
|
- "product:updated"
|
|
- "product:deleted"
|
|
- "shop:disconnected"
|
|
"""
|
|
def create_webhook(shop_id, url, topic, secret) do
|
|
post("/shops/#{shop_id}/webhooks.json", %{
|
|
topic: topic,
|
|
url: url,
|
|
secret: secret
|
|
})
|
|
end
|
|
|
|
@doc """
|
|
List registered webhooks for a shop.
|
|
"""
|
|
def list_webhooks(shop_id) do
|
|
get("/shops/#{shop_id}/webhooks.json")
|
|
end
|
|
|
|
@doc """
|
|
Delete a webhook.
|
|
"""
|
|
def delete_webhook(shop_id, webhook_id) do
|
|
delete("/shops/#{shop_id}/webhooks/#{webhook_id}.json")
|
|
end
|
|
|
|
@doc """
|
|
Download a file from a URL to a local path.
|
|
"""
|
|
def download_file(url, output_path) do
|
|
case Req.get(url, into: File.stream!(output_path), receive_timeout: 60_000) do
|
|
{:ok, %Req.Response{status: status}} when status in 200..299 ->
|
|
{:ok, output_path}
|
|
|
|
{:ok, %Req.Response{status: status}} ->
|
|
File.rm(output_path)
|
|
{:error, {:http_error, status}}
|
|
|
|
{:error, reason} ->
|
|
File.rm(output_path)
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
defp auth_headers do
|
|
[
|
|
{"Authorization", "Bearer #{api_token()}"},
|
|
{"Content-Type", "application/json"}
|
|
]
|
|
end
|
|
end
|