2020-02-18 08:57:00 +01:00
|
|
|
defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
|
|
|
@moduledoc """
|
|
|
|
Handles the resources-related GraphQL calls
|
|
|
|
"""
|
|
|
|
|
2021-09-10 11:35:32 +02:00
|
|
|
alias Mobilizon.{Actors, Resources}
|
2020-02-18 08:57:00 +01:00
|
|
|
alias Mobilizon.Actors.Actor
|
2021-09-28 19:40:37 +02:00
|
|
|
alias Mobilizon.Federation.ActivityPub.Actions
|
2020-02-18 08:57:00 +01:00
|
|
|
alias Mobilizon.Resources.Resource
|
|
|
|
alias Mobilizon.Resources.Resource.Metadata
|
|
|
|
alias Mobilizon.Service.RichMedia.Parser
|
|
|
|
alias Mobilizon.Storage.Page
|
|
|
|
alias Mobilizon.Users.User
|
2021-01-26 10:05:31 +01:00
|
|
|
alias Mobilizon.Web.MediaProxy
|
2020-09-29 09:53:48 +02:00
|
|
|
import Mobilizon.Web.Gettext
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
require Logger
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Find resources for group.
|
|
|
|
|
|
|
|
Returns only if actor requesting is a member of the group
|
|
|
|
"""
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec find_resources_for_group(Actor.t(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Page.t(Resource.t())}
|
2020-02-18 08:57:00 +01:00
|
|
|
def find_resources_for_group(
|
|
|
|
%Actor{id: group_id} = group,
|
|
|
|
%{page: page, limit: limit},
|
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
2020-02-18 08:57:00 +01:00
|
|
|
%Page{} = page <- Resources.get_resources_for_group(group, page, limit) do
|
|
|
|
{:ok, page}
|
|
|
|
else
|
|
|
|
{:member, _} ->
|
|
|
|
find_resources_for_group(nil, nil, nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_resources_for_group(
|
|
|
|
_group,
|
|
|
|
_args,
|
|
|
|
_resolution
|
|
|
|
) do
|
|
|
|
{:ok, %Page{total: 0, elements: []}}
|
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec find_resources_for_parent(Resource.t(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Page.t(Resource.t())}
|
2020-02-18 08:57:00 +01:00
|
|
|
def find_resources_for_parent(
|
|
|
|
%Resource{actor_id: group_id} = parent,
|
2021-06-14 15:12:38 +02:00
|
|
|
%{page: page, limit: limit},
|
2020-02-18 08:57:00 +01:00
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
2021-06-14 15:12:38 +02:00
|
|
|
%Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do
|
2020-02-18 08:57:00 +01:00
|
|
|
{:ok, page}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_resources_for_parent(_parent, _args, _resolution),
|
|
|
|
do: {:ok, %Page{total: 0, elements: []}}
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec get_resource(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Resource.t()} | {:error, :group_not_found | :resource_not_found | String.t()}
|
2020-02-18 08:57:00 +01:00
|
|
|
def get_resource(
|
|
|
|
_parent,
|
|
|
|
%{path: path, username: username},
|
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)},
|
2020-02-18 08:57:00 +01:00
|
|
|
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
|
|
|
{:resource, %Resource{} = resource} <-
|
|
|
|
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
|
|
|
|
{:ok, resource}
|
|
|
|
else
|
2020-10-19 19:21:39 +02:00
|
|
|
{:group, _} -> {:error, :group_not_found}
|
2020-09-29 09:53:48 +02:00
|
|
|
{:member, false} -> {:error, dgettext("errors", "Profile is not member of group")}
|
2020-10-19 19:21:39 +02:00
|
|
|
{:resource, _} -> {:error, :resource_not_found}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_resource(_parent, _args, _resolution) do
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "You need to be logged-in to access resources")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec create_resource(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Resource.t()} | {:error, String.t()}
|
2020-02-18 08:57:00 +01:00
|
|
|
def create_resource(
|
|
|
|
_parent,
|
|
|
|
%{actor_id: group_id} = args,
|
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-10-05 17:43:45 +02:00
|
|
|
if Actors.is_member?(actor_id, group_id) do
|
|
|
|
parent = get_eventual_parent(args)
|
2021-03-24 10:45:29 +01:00
|
|
|
|
2021-10-05 17:43:45 +02:00
|
|
|
if check_resource_owned_by_group(parent, group_id) do
|
|
|
|
case Actions.Create.create(
|
|
|
|
:resource,
|
|
|
|
args
|
|
|
|
|> Map.put(:actor_id, group_id)
|
|
|
|
|> Map.put(:creator_id, actor_id),
|
|
|
|
true,
|
|
|
|
%{}
|
|
|
|
) do
|
|
|
|
{:ok, _, %Resource{} = resource} ->
|
|
|
|
{:ok, resource}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
2021-10-05 17:43:45 +02:00
|
|
|
{:error, _err} ->
|
|
|
|
{:error, dgettext("errors", "Error while creating resource")}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:error, dgettext("errors", "Parent resource doesn't belong to this group")}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:error, dgettext("errors", "Profile is not member of group")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_resource(_parent, _args, _resolution) do
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "You need to be logged-in to create resources")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec update_resource(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Resource.t()} | {:error, String.t()}
|
2020-02-18 08:57:00 +01:00
|
|
|
def update_resource(
|
|
|
|
_parent,
|
|
|
|
%{id: resource_id} = args,
|
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id, url: actor_url}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-09-28 19:40:37 +02:00
|
|
|
case Resources.get_resource_with_preloads(resource_id) do
|
|
|
|
%Resource{actor_id: group_id} = resource ->
|
|
|
|
if Actors.is_member?(actor_id, group_id) do
|
|
|
|
case Actions.Update.update(resource, args, true, %{"actor" => actor_url}) do
|
|
|
|
{:ok, _, %Resource{} = resource} ->
|
|
|
|
{:ok, resource}
|
|
|
|
|
|
|
|
{:error, %Ecto.Changeset{} = err} ->
|
|
|
|
{:error, err}
|
|
|
|
|
|
|
|
{:error, err} when is_atom(err) ->
|
|
|
|
{:error, dgettext("errors", "Unknown error while updating resource")}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:error, dgettext("errors", "Profile is not member of group")}
|
|
|
|
end
|
|
|
|
|
|
|
|
nil ->
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "Resource doesn't exist")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_resource(_parent, _args, _resolution) do
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "You need to be logged-in to update resources")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec delete_resource(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Resource.t()} | {:error, String.t()}
|
2020-02-18 08:57:00 +01:00
|
|
|
def delete_resource(
|
|
|
|
_parent,
|
|
|
|
%{id: resource_id},
|
|
|
|
%{
|
|
|
|
context: %{
|
2021-09-10 11:35:32 +02:00
|
|
|
current_actor: %Actor{id: actor_id} = actor
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
with {:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <-
|
2020-02-18 08:57:00 +01:00
|
|
|
{:resource, Resources.get_resource_with_preloads(resource_id)},
|
|
|
|
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
|
|
|
{:ok, _, %Resource{} = resource} <-
|
2021-09-28 19:40:37 +02:00
|
|
|
Actions.Delete.delete(resource, actor) do
|
2020-02-18 08:57:00 +01:00
|
|
|
{:ok, resource}
|
|
|
|
else
|
|
|
|
{:resource, _} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "Resource doesn't exist")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
{:member, _} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "Profile is not member of group")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_resource(_parent, _args, _resolution) do
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "You need to be logged-in to delete resources")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec preview_resource_link(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, Metadata.t()} | {:error, String.t() | :unknown_resource}
|
2020-02-18 08:57:00 +01:00
|
|
|
def preview_resource_link(
|
|
|
|
_parent,
|
|
|
|
%{resource_url: resource_url},
|
|
|
|
%{
|
|
|
|
context: %{
|
|
|
|
current_user: %User{} = _user
|
|
|
|
}
|
|
|
|
} = _resolution
|
|
|
|
) do
|
2020-10-20 10:06:58 +02:00
|
|
|
case Parser.parse(resource_url) do
|
|
|
|
{:ok, data} when is_map(data) ->
|
|
|
|
{:ok, struct(Metadata, data)}
|
|
|
|
|
2021-03-24 10:45:29 +01:00
|
|
|
{:error, :invalid_parsed_data} ->
|
|
|
|
{:error, dgettext("errors", "Unable to fetch resource details from this URL.")}
|
|
|
|
|
|
|
|
{:error, err} ->
|
2020-10-19 19:21:39 +02:00
|
|
|
Logger.warn("Error while fetching preview from #{inspect(resource_url)}")
|
2021-03-24 10:45:29 +01:00
|
|
|
Logger.debug(inspect(err))
|
2020-10-19 19:21:39 +02:00
|
|
|
{:error, :unknown_resource}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def preview_resource_link(_parent, _args, _resolution) do
|
2020-09-29 09:53:48 +02:00
|
|
|
{:error, dgettext("errors", "You need to be logged-in to view a resource preview")}
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
@spec proxyify_pictures(Metadata.t(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
{:ok, String.t() | nil} | {:error, String.t()}
|
2021-01-26 10:05:31 +01:00
|
|
|
def proxyify_pictures(%Metadata{} = metadata, _args, %{
|
|
|
|
definition: %{schema_node: %{name: name}}
|
|
|
|
}) do
|
|
|
|
case name do
|
|
|
|
"image_remote_url" -> {:ok, proxify_picture(metadata.image_remote_url)}
|
|
|
|
"favicon_url" -> {:ok, proxify_picture(metadata.favicon_url)}
|
|
|
|
_ -> {:error, "Unknown field"}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
@spec get_eventual_parent(map()) :: Resource.t() | nil
|
|
|
|
defp get_eventual_parent(args) do
|
|
|
|
parent = args |> Map.get(:parent_id) |> get_parent_resource()
|
|
|
|
|
|
|
|
case parent do
|
|
|
|
%Resource{} -> parent
|
|
|
|
_ -> nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@spec get_parent_resource(integer | nil) :: nil | Resource.t()
|
|
|
|
defp get_parent_resource(nil), do: nil
|
|
|
|
defp get_parent_resource(parent_id), do: Resources.get_resource(parent_id)
|
|
|
|
|
|
|
|
@spec check_resource_owned_by_group(Resource.t() | nil, integer) :: boolean
|
|
|
|
defp check_resource_owned_by_group(nil, _group_id), do: true
|
|
|
|
|
|
|
|
defp check_resource_owned_by_group(%Resource{actor_id: actor_id}, group_id)
|
|
|
|
when is_binary(group_id),
|
|
|
|
do: actor_id == String.to_integer(group_id)
|
|
|
|
|
|
|
|
defp check_resource_owned_by_group(%Resource{actor_id: actor_id}, group_id)
|
|
|
|
when is_number(group_id),
|
|
|
|
do: actor_id == group_id
|
2021-01-26 10:05:31 +01:00
|
|
|
|
|
|
|
@spec proxify_picture(String.t() | nil) :: String.t() | nil
|
|
|
|
defp proxify_picture(nil), do: nil
|
|
|
|
|
|
|
|
defp proxify_picture(url) do
|
|
|
|
MediaProxy.url(url)
|
|
|
|
end
|
2020-02-18 08:57:00 +01:00
|
|
|
end
|