Add a command to refresh a single actor or all actors

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-06-10 14:28:27 +02:00
parent 23a5ffd3a1
commit c61a54d802
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
2 changed files with 123 additions and 2 deletions

View file

@ -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

View file

@ -210,12 +210,23 @@ defmodule Mobilizon.Actors do
Conflicts on actor's URL/AP ID, replaces keys, avatar and banner, name and summary. 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()} @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 = insert =
data data
|> Actor.remote_actor_creation_changeset() |> Actor.remote_actor_creation_changeset()
|> Repo.insert( |> 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] conflict_target: [:url]
) )
@ -232,6 +243,18 @@ defmodule Mobilizon.Actors do
end end
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 def delete_actor(%Actor{} = actor) do
Workers.Background.enqueue("delete_actor", %{"actor_id" => actor.id}) Workers.Background.enqueue("delete_actor", %{"actor_id" => actor.id})
end end