# SPDX-FileCopyrightText: 2019 ash_json_api contributors # # SPDX-License-Identifier: MIT if Code.ensure_loaded?(Igniter) do defmodule AshJsonApi.Igniter do @moduledoc "Codemods and utilities for working with AshJsonApi & Igniter" @doc "Returns the AshJsonApi router containing the domain in question, or a list of all AshJsonApi schemas" def find_ash_json_api_router(igniter, domain) do {igniter, modules} = ash_json_api_routers(igniter) modules |> Enum.find(fn module -> with {:ok, {_igniter, _source, zipper}} <- Igniter.Project.Module.find_module(igniter, module), {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshJsonApi.Router), {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1), {:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :domains), {:ok, _zipper} <- Igniter.Code.List.move_to_list_item( zipper, &Igniter.Code.Common.nodes_equal?(&1, domain) ) do true else _ -> false end end) |> case do nil -> {:error, igniter, modules} module -> {:ok, igniter, module} end end @doc "Sets up an `AshJsonApi.Router` for AshJsonApi" def setup_ash_json_api_router(igniter, ash_phoenix_router_name \\ nil) do ash_phoenix_router_name = ash_phoenix_router_name || Igniter.Libs.Phoenix.web_module_name(igniter, "AshJsonApiRouter") {igniter, domains} = Ash.Domain.Igniter.list_domains(igniter) {igniter, domains} = Enum.reduce(domains, {igniter, []}, fn domain, {igniter, list} -> case Spark.Igniter.has_extension( igniter, domain, Ash.Domain, :extensions, AshJsonApi.Domain ) do {igniter, true} -> {igniter, [domain | list]} {igniter, false} -> {igniter, list} end end) igniter |> Igniter.Project.Module.find_and_update_or_create_module( ash_phoenix_router_name, """ use AshJsonApi.Router, domains: #{inspect(domains)}, open_api: "/open_api" """, fn zipper -> # Should never get here {:ok, zipper} end ) end @doc "Sets up the phoenix module for AshJsonApi" def setup_phoenix(igniter, ash_phoenix_router_name \\ nil) do ash_phoenix_router_name = ash_phoenix_router_name || Igniter.Libs.Phoenix.web_module_name(igniter, "AshJsonApiRouter") case Igniter.Libs.Phoenix.select_router(igniter) do {igniter, nil} -> igniter |> Igniter.add_warning(""" No Phoenix router found, skipping Phoenix installation. See the Getting Started guide for instructions on installing AshJsonApi with `plug`. If you have yet to set up Phoenix, you'll have to do that manually and then rerun this installer. """) {igniter, router} -> igniter |> update_endpoints(router) |> Igniter.Project.Config.configure( "config.exs", :mime, [:extensions], %{ "json" => "application/vnd.api+json" }, updater: fn zipper -> Igniter.Code.Map.set_map_key( zipper, "json", "application/vnd.api+json", fn zipper -> {:ok, zipper} end ) end ) |> Igniter.Project.Config.configure( "config.exs", :mime, [:types], %{ "application/vnd.api+json" => ["json"] }, updater: fn zipper -> Igniter.Code.Map.set_map_key( zipper, "application/vnd.api+json", ["json"], fn zipper -> zipper = if Igniter.Code.List.list?(zipper) do zipper else Sourceror.Zipper.replace(zipper, [zipper.node]) end Igniter.Code.List.prepend_new_to_list(zipper, "json") end ) end ) |> Igniter.Libs.Phoenix.add_pipeline(:api, "plug :accepts, [\"json\"]", router: router, warn_on_present?: false ) |> Igniter.Libs.Phoenix.add_scope( "/api/json", """ pipe_through [:api] forward "/swaggerui", OpenApiSpex.Plug.SwaggerUI, path: "/api/json/open_api", default_model_expand_depth: 4 forward "/", #{inspect(ash_phoenix_router_name)} """, router: router ) end end def setup_routes_alias(igniter) do # if `Phoenix.Router` is not loaded we don't # know what version they are using and conservatively # do not add an alias that will cause an error if Code.ensure_loaded?(Phoenix.Router) do # Phoenix >= 1.8 uses the new formatted routes feature and # so we do not need this alias if function_exported?(Phoenix.Router, :__formatted_routes__, 1) do igniter else Igniter.Project.TaskAliases.add_alias( igniter, "phx.routes", ["phx.routes", "ash_json_api.routes"], if_exists: {:append, "ash_json_api.routes"} ) end else igniter end end defp update_endpoints(igniter, router) do {igniter, endpoints_that_need_parser} = Igniter.Libs.Phoenix.endpoints_for_router(igniter, router) Enum.reduce(endpoints_that_need_parser, igniter, fn endpoint, igniter -> Igniter.Project.Module.find_and_update_module!(igniter, endpoint, fn zipper -> case Igniter.Code.Function.move_to_function_call_in_current_scope( zipper, :plug, 2, &Igniter.Code.Function.argument_equals?(&1, 0, Plug.Parsers) ) do {:ok, zipper} -> with {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1), {:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :parsers), {:ok, zipper} <- Igniter.Code.List.append_new_to_list(zipper, AshJsonApi.Plug.Parser) do {:ok, zipper} else _ -> {:warning, "Could not add `AshJsonApi.Plug.Parser` to parsers in endpoint #{endpoint}. Please make this change manually."} end :error -> case parser_location(zipper) do {:ok, zipper} -> {:ok, Igniter.Code.Common.add_code(zipper, """ plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json, AshJsonApi.Plug.Parser], pass: ["*/*"], json_decoder: Jason """)} _ -> {:warning, "Could not add `AshJsonApi.Plug.Parser` to parsers in endpoint #{endpoint}. Please make this change manually."} end end end) end) end defp parser_location(zipper) do with :error <- Igniter.Code.Function.move_to_function_call_in_current_scope( zipper, :plug, [1, 2], &Igniter.Code.Function.argument_equals?(&1, 0, Plug.Telemetry) ), :error <- Igniter.Code.Function.move_to_function_call_in_current_scope( zipper, :plug, [1, 2] ) do Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint) :error end end @doc "Returns all modules that `use AshJsonApi.Router`" def ash_json_api_routers(igniter) do Igniter.Project.Module.find_all_matching_modules(igniter, fn _name, zipper -> match?({:ok, _}, Igniter.Code.Module.move_to_use(zipper, AshJsonApi.Router)) end) end end end