2020-05-02 10:35:12 +12:00
|
|
|
defmodule AshGraphql.Graphql.Resolver do
|
2020-08-14 10:55:34 +12:00
|
|
|
@moduledoc false
|
2020-05-02 10:35:12 +12:00
|
|
|
def resolve(
|
|
|
|
%{arguments: %{id: id}, context: context} = resolution,
|
|
|
|
{api, resource, :get, action}
|
|
|
|
) do
|
2020-09-29 02:42:36 +13:00
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.authorize?(api),
|
|
|
|
action: action
|
|
|
|
]
|
2020-06-06 06:41:00 +12:00
|
|
|
|
|
|
|
result = api.get(resource, id, opts)
|
2020-05-02 10:35:12 +12:00
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
|
|
end
|
|
|
|
|
|
|
|
def resolve(
|
2020-08-14 09:39:59 +12:00
|
|
|
%{arguments: %{limit: limit, offset: offset} = args, context: context} = resolution,
|
|
|
|
{api, resource, :list, action}
|
2020-05-02 10:35:12 +12:00
|
|
|
) do
|
2020-09-29 02:42:36 +13:00
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.authorize?(api),
|
|
|
|
action: action
|
|
|
|
]
|
2020-06-06 06:41:00 +12:00
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
query =
|
2020-05-30 20:57:39 +12:00
|
|
|
resource
|
|
|
|
|> Ash.Query.limit(limit)
|
|
|
|
|> Ash.Query.offset(offset)
|
2020-08-14 09:39:59 +12:00
|
|
|
|
|
|
|
query =
|
|
|
|
case Map.fetch(args, :filter) do
|
|
|
|
{:ok, filter} ->
|
|
|
|
case Jason.decode(filter) do
|
|
|
|
{:ok, decoded} ->
|
|
|
|
Ash.Query.filter(query, to_snake_case(decoded))
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
raise "Error parsing filter: #{inspect(error)}"
|
|
|
|
end
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
query
|
|
|
|
end
|
|
|
|
|
|
|
|
result =
|
|
|
|
query
|
2020-06-06 06:41:00 +12:00
|
|
|
|> api.read(opts)
|
2020-05-30 20:57:39 +12:00
|
|
|
|> case do
|
|
|
|
{:ok, results} ->
|
2020-08-14 10:55:34 +12:00
|
|
|
{:ok, %{results: results, count: Enum.count(results)}}
|
2020-05-30 20:57:39 +12:00
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
2020-05-02 10:35:12 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
|
|
end
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
def mutate(
|
|
|
|
%{arguments: %{input: input}, context: context} = resolution,
|
|
|
|
{api, resource, :create, action}
|
|
|
|
) do
|
2020-08-15 02:20:47 +12:00
|
|
|
{attributes, relationships} = split_attrs_and_rels(input, resource)
|
2020-08-14 09:39:59 +12:00
|
|
|
|
|
|
|
changeset = Ash.Changeset.new(resource, attributes)
|
|
|
|
|
|
|
|
changeset_with_relationships =
|
|
|
|
Enum.reduce(relationships, changeset, fn {relationship, replacement}, changeset ->
|
|
|
|
Ash.Changeset.replace_relationship(changeset, relationship, replacement)
|
|
|
|
end)
|
|
|
|
|
2020-09-29 02:42:36 +13:00
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.authorize?(api),
|
|
|
|
action: action
|
|
|
|
]
|
2020-08-14 09:39:59 +12:00
|
|
|
|
|
|
|
result =
|
2020-09-24 12:54:57 +12:00
|
|
|
case api.create(changeset_with_relationships, opts) do
|
|
|
|
{:ok, value} ->
|
|
|
|
{:ok, %{result: value, errors: []}}
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
{:error, error} ->
|
|
|
|
{:ok, %{result: nil, errors: to_errors(error)}}
|
|
|
|
end
|
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
|
|
end
|
|
|
|
|
|
|
|
def mutate(
|
|
|
|
%{arguments: %{id: id, input: input}, context: context} = resolution,
|
|
|
|
{api, resource, :update, action}
|
|
|
|
) do
|
|
|
|
case api.get(resource, id) do
|
|
|
|
nil ->
|
|
|
|
{:ok, %{result: nil, errors: [to_errors("not found")]}}
|
|
|
|
|
|
|
|
initial ->
|
2020-08-15 02:20:47 +12:00
|
|
|
{attributes, relationships} = split_attrs_and_rels(input, resource)
|
2020-08-14 09:39:59 +12:00
|
|
|
changeset = Ash.Changeset.new(initial, attributes)
|
|
|
|
|
|
|
|
changeset_with_relationships =
|
|
|
|
Enum.reduce(relationships, changeset, fn {relationship, replacement}, changeset ->
|
|
|
|
Ash.Changeset.replace_relationship(changeset, relationship, replacement)
|
|
|
|
end)
|
|
|
|
|
2020-09-29 02:42:36 +13:00
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.authorize?(api),
|
|
|
|
action: action
|
|
|
|
]
|
2020-08-14 09:39:59 +12:00
|
|
|
|
|
|
|
result =
|
2020-09-24 12:54:57 +12:00
|
|
|
case api.update(changeset_with_relationships, opts) do
|
|
|
|
{:ok, value} ->
|
|
|
|
{:ok, %{result: value, errors: []}}
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
{:error, error} ->
|
|
|
|
{:ok, %{result: nil, errors: List.wrap(error)}}
|
|
|
|
end
|
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def mutate(%{arguments: %{id: id}, context: context} = resolution, {api, resource, action}) do
|
|
|
|
case api.get(resource, id) do
|
|
|
|
nil ->
|
|
|
|
{:ok, %{result: nil, errors: [to_errors("not found")]}}
|
|
|
|
|
|
|
|
initial ->
|
|
|
|
opts =
|
|
|
|
if AshGraphql.Api.authorize?(api) do
|
|
|
|
[actor: Map.get(context, :actor), action: action]
|
|
|
|
else
|
|
|
|
[action: action]
|
|
|
|
end
|
|
|
|
|
|
|
|
result =
|
|
|
|
case api.destroy(initial, opts) do
|
|
|
|
:ok -> {:ok, %{result: initial, errors: []}}
|
|
|
|
{:error, error} -> {:ok, %{result: nil, errors: to_errors(error)}}
|
|
|
|
end
|
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-15 02:20:47 +12:00
|
|
|
defp split_attrs_and_rels(input, resource) do
|
|
|
|
Enum.reduce(input, {%{}, %{}}, fn {key, value}, {attrs, rels} ->
|
|
|
|
if Ash.Resource.attribute(resource, key) do
|
|
|
|
{Map.put(attrs, key, value), rels}
|
|
|
|
else
|
|
|
|
{attrs, Map.put(rels, key, value)}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
defp to_errors(errors) do
|
|
|
|
errors
|
|
|
|
|> List.wrap()
|
|
|
|
|> Enum.map(fn error ->
|
|
|
|
cond do
|
|
|
|
is_binary(error) ->
|
|
|
|
%{message: error}
|
|
|
|
|
|
|
|
Exception.exception?(error) ->
|
|
|
|
%{
|
|
|
|
message: Exception.message(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
true ->
|
|
|
|
%{message: "something went wrong"}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
def resolve_assoc(
|
2020-09-29 02:42:36 +13:00
|
|
|
%{source: parent, arguments: args, context: %{ash_loader: loader} = context} = resolution,
|
2020-09-24 12:54:57 +12:00
|
|
|
{api, relationship}
|
|
|
|
) do
|
2020-09-29 02:42:36 +13:00
|
|
|
api_opts = [actor: Map.get(context, :actor), authorize?: AshGraphql.Api.authorize?(api)]
|
|
|
|
|
|
|
|
opts = [
|
|
|
|
query: apply_load_arguments(args, Ash.Query.new(relationship.destination)),
|
|
|
|
api_opts: api_opts
|
|
|
|
]
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
{batch_key, parent} = {{relationship.name, opts}, parent}
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2020-09-29 02:42:36 +13:00
|
|
|
do_dataloader(resolution, loader, api, batch_key, args, parent)
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
defp do_dataloader(
|
|
|
|
resolution,
|
|
|
|
loader,
|
|
|
|
api,
|
|
|
|
batch_key,
|
|
|
|
args,
|
2020-09-29 02:42:36 +13:00
|
|
|
parent
|
2020-09-24 12:54:57 +12:00
|
|
|
) do
|
|
|
|
loader = Dataloader.load(loader, api, batch_key, parent)
|
|
|
|
|
|
|
|
fun = fn loader ->
|
2020-09-29 02:42:36 +13:00
|
|
|
callback = default_callback(loader)
|
2020-09-24 12:54:57 +12:00
|
|
|
|
|
|
|
loader
|
|
|
|
|> Dataloader.get(api, batch_key, parent)
|
|
|
|
|> callback.(parent, args)
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
{:middleware, Absinthe.Middleware.Dataloader, {loader, fun}}
|
|
|
|
)
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
defp default_callback(%{options: loader_options}) do
|
|
|
|
if loader_options[:get_policy] == :tuples do
|
|
|
|
fn result, _parent, _args -> result end
|
|
|
|
else
|
|
|
|
fn result, _parent, _args -> {:ok, result} end
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
defp apply_load_arguments(arguments, query) do
|
|
|
|
Enum.reduce(arguments, query, fn
|
|
|
|
{:limit, limit}, query ->
|
|
|
|
Ash.Query.limit(query, limit)
|
2020-08-15 02:20:47 +12:00
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
{:offset, offset}, query ->
|
|
|
|
Ash.Query.offset(query, offset)
|
2020-08-15 02:20:47 +12:00
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
{:filter, value}, query ->
|
2020-08-15 02:20:47 +12:00
|
|
|
decode_and_filter(query, value)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp decode_and_filter(query, value) do
|
|
|
|
case Jason.decode(value) do
|
|
|
|
{:ok, decoded} ->
|
|
|
|
Ash.Query.filter(query, to_snake_case(decoded))
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
raise "Error parsing filter: #{inspect(error)}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
defp to_snake_case(map) when is_map(map) do
|
|
|
|
Enum.into(map, %{}, fn {key, value} ->
|
|
|
|
{Macro.underscore(key), to_snake_case(value)}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp to_snake_case(list) when is_list(list) do
|
|
|
|
Enum.map(list, &to_snake_case/1)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp to_snake_case(other), do: other
|
2020-05-02 10:35:12 +12:00
|
|
|
|
|
|
|
defp to_resolution({:ok, value}), do: {:ok, value}
|
2020-08-14 09:39:59 +12:00
|
|
|
|
|
|
|
defp to_resolution({:error, error}),
|
|
|
|
do: {:error, error |> List.wrap() |> Enum.map(&Exception.message(&1))}
|
2020-05-02 10:35:12 +12:00
|
|
|
end
|