diff --git a/lib/ash_admin/actor_plug.ex b/lib/ash_admin/actor_plug.ex index 0e5c0f1..db414d4 100644 --- a/lib/ash_admin/actor_plug.ex +++ b/lib/ash_admin/actor_plug.ex @@ -2,190 +2,23 @@ defmodule AshAdmin.ActorPlug do @moduledoc false @behaviour Plug - require Ash.Query - import AshAdmin.Helpers + @plug Application.compile_env(:ash_admin, :actor_plug, AshAdmin.ActorPlug.Plug) - @plug Application.compile_env(:ash_admin, :actor_plug, __MODULE__) - - @callback set_actor_session(conn :: Plug.Conn.t(), session :: map) :: Plug.Conn.t() + @callback set_actor_session(conn :: Plug.Conn.t()) :: Plug.Conn.t() @callback actor_assigns(socket :: Phoenix.LiveView.Socket.t(), session :: map) :: Keyword.t() def init(opts), do: opts def call(conn, _opts) do - case conn.cookies do - %{"actor_resource" => "undefined"} -> - conn - - session -> - set_actor_session(conn, session) - end + set_actor_session(conn) end - if @plug == __MODULE__ do - def actor_assigns(socket, session) do - otp_app = socket.endpoint.config(:otp_app) - apis = apis(otp_app) - - actor_paused = - if is_nil(session["actor_paused"]) do - true - else - session_bool(session["actor_paused"]) - end - - [ - actor: actor_from_session(socket.endpoint, session), - actor_api: actor_api_from_session(socket.endpoint, session), - actor_resources: actor_resources(apis), - authorizing: session_bool(session["actor_authorizing"]) , - actor_paused: actor_paused - ] - end - else - def actor_assigns(socket, session) do - @plug.actor_assigns(socket, session) - end + def actor_assigns(socket, session) do + @plug.actor_assigns(socket, session) end - if @plug == __MODULE__ do - defp set_actor_session( - conn, - session - ) do - case session do - %{ - "actor_resource" => resource, - "actor_api" => api, - "actor_action" => action, - "actor_primary_key" => primary_key - } -> - authorizing = session["actor_authorizing"] || false - - actor_paused = - if is_nil(session["actor_paused"]) do - true - else - session["actor_paused"] - end - - actor = actor_from_session(conn.private.phoenix_endpoint, session) - - authorizing = session_bool(authorizing) - actor_paused = session_bool(actor_paused) - - conn - |> Plug.Conn.put_session(:actor_resource, resource) - |> Plug.Conn.put_session(:actor_api, api) - |> Plug.Conn.put_session(:actor_action, action) - |> Plug.Conn.put_session(:actor_primary_key, primary_key) - |> Plug.Conn.put_session(:actor_authorizing, authorizing) - |> Plug.Conn.put_session(:actor_paused, actor_paused) - |> Plug.Conn.assign(:actor, actor) - |> Plug.Conn.assign(:authorizing, authorizing || false) - |> Plug.Conn.assign(:actor_paused, actor_paused) - |> Plug.Conn.assign(:authorizing, authorizing) - - _ -> - conn - end - end - else - defp set_actor_session(conn, session) do - @plug.set_actor_session(conn, session) - end - end - - defp actor_resources(apis) do - apis - |> Enum.flat_map(fn api -> - api - |> Ash.Api.Info.resources() - |> Enum.filter(fn resource -> - AshAdmin.Helpers.primary_action(resource, :read) && AshAdmin.Resource.actor?(resource) - end) - |> Enum.map(fn resource -> {api, resource} end) - end) - end - - defp apis(otp_app) do - otp_app - |> Application.get_env(:ash_apis) - |> Enum.filter(&AshAdmin.Api.show?/1) - end - - defp actor_api_from_session(endpoint, %{"actor_api" => api}) do - otp_app = endpoint.config(:otp_app) - apis = Application.get_env(otp_app, :ash_apis) - - Enum.find(apis, fn allowed_api -> - AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api - end) - end - - defp actor_api_from_session(_, _), do: nil - - defp actor_from_session(endpoint, %{ - "actor_resource" => resource, - "actor_api" => api, - "actor_primary_key" => primary_key, - "actor_action" => action - }) - when not is_nil(resource) and not is_nil(api) do - otp_app = endpoint.config(:otp_app) - apis = Application.get_env(otp_app, :ash_apis) - - api = - Enum.find(apis, fn allowed_api -> - AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api - end) - - resource = - if api do - api - |> Ash.Api.Info.resources() - |> Enum.find(fn api_resource -> - AshAdmin.Resource.name(api_resource) == resource - end) - end - - if api && resource do - action = - if action do - Ash.Resource.Info.action(resource, String.to_existing_atom(action), :read) - end - - case decode_primary_key(resource, primary_key) do - :error -> - nil - - {:ok, filter} -> - resource - |> Ash.Query.filter(^filter) - |> api.read_one!(action: action, authorize?: false) - end - end - end - - defp actor_from_session(_, _), do: nil - - defp session_bool(value) do - case value do - "true" -> - true - - "false" -> - false - - "undefined" -> - false - - boolean when is_boolean(boolean) -> - boolean - - nil -> - false - end + def set_actor_session(conn) do + @plug.set_actor_session(conn) end end diff --git a/lib/ash_admin/actor_plug/plug.ex b/lib/ash_admin/actor_plug/plug.ex new file mode 100644 index 0000000..c48b970 --- /dev/null +++ b/lib/ash_admin/actor_plug/plug.ex @@ -0,0 +1,165 @@ +defmodule AshAdmin.ActorPlug.Plug do + @behaviour AshAdmin.ActorPlug + import AshAdmin.Helpers + require Ash.Query + + @impl true + def actor_assigns(socket, session) do + otp_app = socket.endpoint.config(:otp_app) + apis = apis(otp_app) + + actor_paused = + if is_nil(session["actor_paused"]) do + true + else + session_bool(session["actor_paused"]) + end + + [ + actor: actor_from_session(socket.endpoint, session), + actor_api: actor_api_from_session(socket.endpoint, session), + actor_resources: actor_resources(apis), + authorizing: session_bool(session["actor_authorizing"]), + actor_paused: actor_paused + ] + end + + @impl true + def set_actor_session(conn) do + case conn.cookies do + %{"actor_resource" => "undefined"} -> + conn + + session -> + case session do + %{ + "actor_resource" => resource, + "actor_api" => api, + "actor_action" => action, + "actor_primary_key" => primary_key + } -> + authorizing = session["actor_authorizing"] || false + + actor_paused = + if is_nil(session["actor_paused"]) do + true + else + session["actor_paused"] + end + + actor = actor_from_session(conn.private.phoenix_endpoint, session) + + authorizing = session_bool(authorizing) + actor_paused = session_bool(actor_paused) + + conn + |> Plug.Conn.put_session(:actor_resource, resource) + |> Plug.Conn.put_session(:actor_api, api) + |> Plug.Conn.put_session(:actor_action, action) + |> Plug.Conn.put_session(:actor_primary_key, primary_key) + |> Plug.Conn.put_session(:actor_authorizing, authorizing) + |> Plug.Conn.put_session(:actor_paused, actor_paused) + |> Plug.Conn.assign(:actor, actor) + |> Plug.Conn.assign(:authorizing, authorizing || false) + |> Plug.Conn.assign(:actor_paused, actor_paused) + |> Plug.Conn.assign(:authorizing, authorizing) + + _ -> + conn + end + end + end + + defp session_bool(value) do + case value do + "true" -> + true + + "false" -> + false + + "undefined" -> + false + + boolean when is_boolean(boolean) -> + boolean + + nil -> + false + end + end + + defp actor_resources(apis) do + apis + |> Enum.flat_map(fn api -> + api + |> Ash.Api.Info.resources() + |> Enum.filter(fn resource -> + AshAdmin.Helpers.primary_action(resource, :read) && AshAdmin.Resource.actor?(resource) + end) + |> Enum.map(fn resource -> {api, resource} end) + end) + end + + defp apis(otp_app) do + otp_app + |> Application.get_env(:ash_apis) + |> Enum.filter(&AshAdmin.Api.show?/1) + end + + defp actor_api_from_session(endpoint, %{"actor_api" => api}) do + otp_app = endpoint.config(:otp_app) + apis = Application.get_env(otp_app, :ash_apis) + + Enum.find(apis, fn allowed_api -> + AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api + end) + end + + defp actor_api_from_session(_, _), do: nil + + defp actor_from_session(endpoint, %{ + "actor_resource" => resource, + "actor_api" => api, + "actor_primary_key" => primary_key, + "actor_action" => action + }) + when not is_nil(resource) and not is_nil(api) do + otp_app = endpoint.config(:otp_app) + apis = Application.get_env(otp_app, :ash_apis) + + api = + Enum.find(apis, fn allowed_api -> + AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api + end) + + resource = + if api do + api + |> Ash.Api.Info.resources() + |> Enum.find(fn api_resource -> + AshAdmin.Resource.name(api_resource) == resource + end) + end + + if api && resource do + action = + if action do + Ash.Resource.Info.action(resource, String.to_existing_atom(action), :read) + end + + case decode_primary_key(resource, primary_key) do + :error -> + nil + + {:ok, filter} -> + resource + |> Ash.Query.filter(^filter) + |> api.read_one!(action: action, authorize?: false) + end + end + end + + defp actor_from_session(_, _), do: nil + +end diff --git a/lib/ash_admin/router.ex b/lib/ash_admin/router.ex index 532c15b..184bd42 100644 --- a/lib/ash_admin/router.ex +++ b/lib/ash_admin/router.ex @@ -64,7 +64,8 @@ defmodule AshAdmin.Router do live_socket_path = Keyword.get(opts, :live_socket_path, "/live") live_session :ash_admin, - session: {AshAdmin.Router, :__session__, [%{"prefix" => path}]}, + on_mount: List.wrap(opts[:on_mount]), + session: [{AshAdmin.Router, :__session__, [%{"prefix" => path}, List.wrap(opts[:session])]}], root_layout: {AshAdmin.LayoutView, :root} do live( "#{path}/*route", @@ -87,9 +88,14 @@ defmodule AshAdmin.Router do ] @doc false - def __session__(conn, [session]), do: __session__(conn, session) + def __session__(conn, [session, additional_hooks]), do: __session__(conn, session, additional_hooks) + + def __session__(conn, session, additional_hooks \\ []) do + session = + Enum.reduce(additional_hooks, session, fn {m, f, a}, acc -> + Map.merge(acc, apply(m, f, [conn | a]) || %{}) + end) - def __session__(conn, session) do session = Map.put(session, "request_path", conn.request_path) Enum.reduce(@cookies_to_replicate, session, fn cookie, session ->