From c61a54d80294de5088b1291f7bb8ce16144526f2 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 10 Jun 2020 14:28:27 +0200 Subject: [PATCH] Add a command to refresh a single actor or all actors Signed-off-by: Thomas Citharel --- lib/mix/tasks/mobilizon/actors/refresh.ex | 98 +++++++++++++++++++++++ lib/mobilizon/actors/actors.ex | 27 ++++++- 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 lib/mix/tasks/mobilizon/actors/refresh.ex diff --git a/lib/mix/tasks/mobilizon/actors/refresh.ex b/lib/mix/tasks/mobilizon/actors/refresh.ex new file mode 100644 index 000000000..55efdbc17 --- /dev/null +++ b/lib/mix/tasks/mobilizon/actors/refresh.ex @@ -0,0 +1,98 @@ +defmodule Mix.Tasks.Mobilizon.Actors.Refresh do + @moduledoc """ + Task to display an actor details + """ + use Mix.Task + alias Mobilizon.Actors.Actor + alias Mobilizon.Federation.ActivityPub + alias Mobilizon.Storage.Repo + import Ecto.Query + require Logger + + @shortdoc "Refresh an actor or all actors" + + @impl Mix.Task + def run(["--all" | options]) do + {options, [], []} = + OptionParser.parse( + options, + strict: [ + verbose: :boolean + ], + aliases: [ + v: :verbose + ] + ) + + verbose = Keyword.get(options, :verbose, false) + + Mix.Task.run("app.start") + + total = count_actors() + + Mix.shell().info(""" + #{total} actors to process + """) + + query = from(a in Actor, where: not is_nil(a.domain)) + + {:ok, _res} = + Repo.transaction( + fn -> + query + |> Repo.stream(timeout: :infinity) + |> Stream.map(&"#{&1.preferred_username}@#{&1.domain}") + |> Stream.each( + if verbose, + do: &Logger.info("Processing #{inspect(&1)}"), + else: &Logger.debug("Processing #{inspect(&1)}") + ) + |> Stream.map(fn username -> make_actor(username, verbose) end) + |> Stream.scan(0, fn _, acc -> acc + 1 end) + |> Stream.each(fn index -> + if verbose, + do: Logger.info("#{index}/#{total}"), + else: ProgressBar.render(index, total) + end) + |> Stream.run() + end, + timeout: :infinity + ) + end + + @impl Mix.Task + def run([preferred_username]) do + Mix.Task.run("app.start") + + case ActivityPub.make_actor_from_nickname(preferred_username) do + {:ok, %Actor{}} -> + Mix.shell().info(""" + Actor #{preferred_username} refreshed + """) + + {:actor, nil} -> + Mix.raise("Error: No such actor") + end + end + + @impl Mix.Task + def run(_) do + Mix.raise("mobilizon.actors.refresh requires an username as argument or --all as an option") + end + + @spec make_actor(String.t(), boolean()) :: any() + defp make_actor(username, verbose) do + ActivityPub.make_actor_from_nickname(username) + rescue + _ -> + if verbose do + Logger.warn("Failed to refresh #{username}") + end + + nil + end + + defp count_actors do + Repo.aggregate(from(a in Actor, where: not is_nil(a.domain)), :count) + end +end diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index ae339d4f5..93b82483d 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -210,12 +210,23 @@ defmodule Mobilizon.Actors do Conflicts on actor's URL/AP ID, replaces keys, avatar and banner, name and summary. """ @spec upsert_actor(map, boolean) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()} - def upsert_actor(%{keys: keys, name: name, summary: summary} = data, preload \\ false) do + def upsert_actor( + %{keys: keys, name: name, summary: summary, avatar: avatar, banner: banner} = data, + preload \\ false + ) do insert = data |> Actor.remote_actor_creation_changeset() |> Repo.insert( - on_conflict: [set: [keys: keys, name: name, summary: summary]], + on_conflict: [ + set: [ + keys: keys, + name: name, + summary: summary, + avatar: transform_media_file(avatar), + banner: transform_media_file(banner) + ] + ], conflict_target: [:url] ) @@ -232,6 +243,18 @@ defmodule Mobilizon.Actors do end end + defp transform_media_file(nil), do: nil + + defp transform_media_file(file) do + file = for({key, val} <- file, into: %{}, do: {String.to_atom(key), val}) + + if is_nil(file) do + nil + else + struct(Mobilizon.Media.File, file) + end + end + def delete_actor(%Actor{} = actor) do Workers.Background.enqueue("delete_actor", %{"actor_id" => actor.id}) end