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 length(shops) > 0 -> {: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