defmodule ShopifexWeb.AuthController do @moduledoc """ You can use this module inside of another controller to handle initial iFrame load and shop installation Example: ```elixir defmodule MyAppWeb.AuthController do use MyAppWeb, :controller use ShopifexWeb.AuthController # Thats it! Validation, installation are now handled for you :) end ``` """ @type shop :: %{access_token: String.t(), scope: String.t(), url: String.t()} @doc """ An optional callback called after the installation is completed, the shop is persisted in the database and webhooks are registered. By default, this function redirects the user to the app within their Shopify admin panel. ## Example @impl true def after_install(conn, shop) do # send yourself an e-mail about shop installation # follow default behaviour. super(conn, shop) end """ @callback after_install(Plug.Conn.t(), shop()) :: Plug.Conn.t() @doc """ An optional callback which is called after the shop data has been retrieved from Shopify API. This function should persist the shop data and return a shop record. ## Example @impl true def insert_shop(conn) do # make sure there is only one store in the database because we don't have # a unique index on the url column for some reason. case Shopifex.Shops.get_shop_by_url(shop_url) do nil -> super(shop) shop -> shop end end """ @callback insert_shop(shop()) :: shop() @optional_callbacks after_install: 2, insert_shop: 1 defmacro __using__(_opts) do quote do @behaviour ShopifexWeb.AuthController require Logger def auth(conn, _) do path_prefix = Application.get_env(:shopifex, :path_prefix, "") conn |> redirect(to: path_prefix <> "/?token=" <> Guardian.Plug.current_token(conn)) end def initialize_installation(conn, %{"shop" => shop_url}) do if Regex.match?(~r/^.*\.myshopify\.com/, shop_url) do # check if store is in the system already: case Shopifex.Shops.get_shop_by_url(shop_url) do nil -> Logger.info("Initiating shop installation for #{shop_url}") install_url = "https://#{shop_url}/admin/oauth/authorize?client_id=#{ Application.fetch_env!(:shopifex, :api_key) }&scope=#{Application.fetch_env!(:shopifex, :scopes)}&redirect_uri=#{ Application.fetch_env!(:shopifex, :redirect_uri) }" conn |> redirect(external: install_url) shop -> Logger.info("Initiating shop reinstallation for #{shop_url}") reinstall_url = "https://#{shop_url}/admin/oauth/request_grant?client_id=#{ Application.fetch_env!(:shopifex, :api_key) }&scope=#{Application.fetch_env!(:shopifex, :scopes)}&redirect_uri=#{ Application.fetch_env!(:shopifex, :reinstall_uri) }" conn |> redirect(external: reinstall_url) end else conn |> put_view(ShopifexWeb.AuthView) |> put_layout({ShopifexWeb.LayoutView, "app.html"}) |> put_flash(:error, "Invalid shop URL") |> render("select-store.html") end end @impl ShopifexWeb.AuthController def after_install(conn, shop) do redirect(conn, external: "https://#{shop.url}/admin/apps/#{Application.fetch_env!(:shopifex, :api_key)}" ) end @impl ShopifexWeb.AuthController def insert_shop(shop) do Shopifex.Shops.create_shop(shop) end def install(conn, %{"code" => code, "shop" => shop_url}) do url = "https://#{shop_url}/admin/oauth/access_token" case( HTTPoison.post( url, Jason.encode!(%{ client_id: Application.fetch_env!(:shopifex, :api_key), client_secret: Application.fetch_env!(:shopifex, :secret), code: code }), "Content-Type": "application/json", Accept: "application/json" ) ) do {:ok, response} -> shop = Jason.decode!(response.body, keys: :atoms) |> Map.put(:url, shop_url) |> insert_shop() Shopifex.Shops.configure_webhooks(shop) after_install(conn, shop) error -> raise(Shopifex.InstallError, message: "Installation failed for shop #{shop_url}") end end def update(conn, %{"code" => code, "shop" => shop_url}) do url = "https://#{shop_url}/admin/oauth/access_token" case( HTTPoison.post( url, Jason.encode!(%{ client_id: Application.fetch_env!(:shopifex, :api_key), client_secret: Application.fetch_env!(:shopifex, :secret), code: code }), "Content-Type": "application/json", Accept: "application/json" ) ) do {:ok, response} -> params = Jason.decode!(response.body, keys: :atoms) shop = shop_url |> Shopifex.Shops.get_shop_by_url() |> Shopifex.Shops.update_shop(params) |> Shopifex.Shops.configure_webhooks() redirect(conn, external: "https://#{shop_url}/admin/apps/#{Application.fetch_env!(:shopifex, :api_key)}" ) error -> raise(Shopifex.UpdateError, message: "Update failed for shop #{shop_url}") end end defoverridable after_install: 2, insert_shop: 1 end end end