lib/permit_ecto/resolver.ex

defmodule Permit.Ecto.Resolver do
  use Permit.ResolverBase

  require Ecto.Query

  import Permit.Helpers, only: [resource_module_from_resource: 1]

  @impl Permit.ResolverBase
  def resolve(subject, authorization_module, resource_module, action, %{} = meta, :one) do
    %{base_query: base_query} = meta = ensure_meta_defaults(meta)

    with {_, true} <-
           {:pre_auth, authorized?(subject, authorization_module, resource_module, action)},
         query <- resource_query(subject, authorization_module, resource_module, action, meta),
         resource when not is_nil(resource) <- authorization_module.repo().one(query),
         {_, true} <- {:auth, authorized?(subject, authorization_module, resource, action)} do
      {:authorized, resource}
    else
      {:pre_auth, false} ->
        :unauthorized

      {:auth, false} ->
        :unauthorized

      nil ->
        case check_existence(
               authorization_module,
               resource_module,
               base_query,
               action,
               subject,
               meta
             ) do
          true ->
            :unauthorized

          false ->
            raise Ecto.NoResultsError,
              queryable: base_query.(action, resource_module, subject, meta["params"])
        end
    end
  end

  @impl Permit.ResolverBase
  def resolve(subject, authorization_module, resource_module, action, %{} = meta, :all) do
    meta = ensure_meta_defaults(meta)

    with {_, true} <-
           {:pre_auth, authorized?(subject, authorization_module, resource_module, action)},
         query <- resource_query(subject, authorization_module, resource_module, action, meta),
         list <- authorization_module.repo().all(query) do
      {:authorized, list}
    else
      {:pre_auth, false} -> :unauthorized
    end
  end

  defp resource_query(
         subject,
         authorization_module,
         resource_module,
         action,
         %{
           base_query: base_query,
           finalize_query: finalize_query,
           params: params
         } = _meta
       ) do
    subject
    |> authorization_module.accessible_by!(action, resource_module,
      base_query: base_query,
      params: params
    )
    |> finalize_query.()
  end

  defp ensure_meta_defaults(meta) do
    meta
    |> Map.put_new(:base_query, fn _action, resource_module, _subject, _params ->
      Ecto.Query.from(_ in resource_module)
    end)
    # |> Map.put_new(:finalize_query, &Function.identity/1)
    |> Map.put_new(:finalize_query, fn query, _action, _resource_module, _subject, _params ->
      query
    end)
  end

  defp check_existence(authorization_module, resource, base_query, action, subject, meta) do
    with module <- resource_module_from_resource(resource),
         query <- base_query.(action, module, subject, meta[:params]) do
      authorization_module.repo.exists?(query)
    end
  end
end