diff --git a/lib/mix/tasks/mobilizon/actors.ex b/lib/mix/tasks/mobilizon/actors.ex
index 1e3bb1ce2..ccea79c73 100644
--- a/lib/mix/tasks/mobilizon/actors.ex
+++ b/lib/mix/tasks/mobilizon/actors.ex
@@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Actors do
use Mix.Task
alias Mix.Tasks
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Manages Mobilizon actors"
@impl Mix.Task
def run(_) do
- Mix.shell().info("\nAvailable tasks:")
- Tasks.Help.run(["--search", "mobilizon.actors."])
+ shell_info("\nAvailable tasks:")
+
+ if mix_shell?() do
+ Tasks.Help.run(["--search", "mobilizon.actors."])
+ else
+ show_subtasks_for_module(__MODULE__)
+ end
end
end
diff --git a/lib/mix/tasks/mobilizon/actors/refresh.ex b/lib/mix/tasks/mobilizon/actors/refresh.ex
index e384be889..d01c23710 100644
--- a/lib/mix/tasks/mobilizon/actors/refresh.ex
+++ b/lib/mix/tasks/mobilizon/actors/refresh.ex
@@ -7,6 +7,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Repo
import Ecto.Query
+ import Mix.Tasks.Mobilizon.Common
require Logger
@shortdoc "Refresh an actor or all actors"
@@ -26,11 +27,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
verbose = Keyword.get(options, :verbose, false)
- Mix.Task.run("app.start")
+ start_mobilizon()
total = count_actors()
- Mix.shell().info("""
+ shell_info("""
#{total} actors to process
""")
@@ -62,22 +63,22 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
@impl Mix.Task
def run([preferred_username]) do
- Mix.Task.run("app.start")
+ start_mobilizon()
case ActivityPub.make_actor_from_nickname(preferred_username) do
{:ok, %Actor{}} ->
- Mix.shell().info("""
+ shell_info("""
Actor #{preferred_username} refreshed
""")
{:actor, nil} ->
- Mix.raise("Error: No such actor")
+ shell_error("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")
+ shell_error("mobilizon.actors.refresh requires an username as argument or --all as an option")
end
@spec make_actor(String.t(), boolean()) :: any()
diff --git a/lib/mix/tasks/mobilizon/actors/show.ex b/lib/mix/tasks/mobilizon/actors/show.ex
index 8abab9e78..73f088796 100644
--- a/lib/mix/tasks/mobilizon/actors/show.ex
+++ b/lib/mix/tasks/mobilizon/actors/show.ex
@@ -5,16 +5,17 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do
use Mix.Task
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Show a Mobilizon user details"
@impl Mix.Task
def run([preferred_username]) do
- Mix.Task.run("app.start")
+ start_mobilizon()
case {:actor, Actors.get_actor_by_name_with_preload(preferred_username)} do
{:actor, %Actor{} = actor} ->
- Mix.shell().info("""
+ shell_info("""
Informations for the actor #{actor.preferred_username}:
- Type: #{actor.type}
- Domain: #{if is_nil(actor.domain), do: "Local", else: actor.domain}
@@ -24,11 +25,11 @@ defmodule Mix.Tasks.Mobilizon.Actors.Show do
""")
{:actor, nil} ->
- Mix.raise("Error: No such actor")
+ shell_error("Error: No such actor")
end
end
def run(_) do
- Mix.raise("mobilizon.actors.show requires an username as argument")
+ shell_error("mobilizon.actors.show requires an username as argument")
end
end
diff --git a/lib/mix/tasks/mobilizon/common.ex b/lib/mix/tasks/mobilizon/common.ex
index 074b8d547..3f292dcc8 100644
--- a/lib/mix/tasks/mobilizon/common.ex
+++ b/lib/mix/tasks/mobilizon/common.ex
@@ -8,32 +8,107 @@ defmodule Mix.Tasks.Mobilizon.Common do
Common functions to be reused in mix tasks
"""
- def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
- display = if defname || defval, do: "#{prompt} [#{defname || defval}]", else: "#{prompt}"
-
- Keyword.get(options, opt) ||
- case Mix.shell().prompt(display) do
- "\n" ->
- case defval do
- nil ->
- get_option(options, opt, prompt, defval)
-
- defval ->
- defval
- end
-
- opt ->
- String.trim(opt)
- end
- end
-
def start_mobilizon do
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
{:ok, _} = Application.ensure_all_started(:mobilizon)
end
+ def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
+ Keyword.get(options, opt) || shell_prompt(prompt, defval, defname)
+ end
+
+ def shell_prompt(prompt, defval \\ nil, defname \\ nil) do
+ prompt_message = "#{prompt} [#{defname || defval}] "
+
+ input =
+ if mix_shell?(),
+ do: Mix.shell().prompt(prompt_message),
+ else: :io.get_line(prompt_message)
+
+ case input do
+ "\n" ->
+ case defval do
+ nil ->
+ shell_prompt(prompt, defval, defname)
+
+ defval ->
+ defval
+ end
+
+ input ->
+ String.trim(input)
+ end
+ end
+
+ def shell_yes?(message) do
+ if mix_shell?(),
+ do: Mix.shell().yes?("Continue?"),
+ else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
+ end
+
+ def shell_info(message) do
+ if mix_shell?(),
+ do: Mix.shell().info(message),
+ else: IO.puts(message)
+ end
+
+ def shell_error(message) do
+ if mix_shell?(),
+ do: Mix.shell().error(message),
+ else: IO.puts(:stderr, message)
+ end
+
+ @doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)"
+ def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0)
+
def escape_sh_path(path) do
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
end
+
+ @type task_module :: atom
+
+ @doc """
+ Gets the shortdoc for the given task `module`.
+ Returns the shortdoc or `nil`.
+ """
+ @spec shortdoc(task_module) :: String.t() | nil
+ def shortdoc(module) when is_atom(module) do
+ case List.keyfind(module.__info__(:attributes), :shortdoc, 0) do
+ {:shortdoc, [shortdoc]} -> shortdoc
+ _ -> nil
+ end
+ end
+
+ def show_subtasks_for_module(module_name) do
+ tasks = list_subtasks_for_module(module_name)
+
+ max = Enum.reduce(tasks, 0, fn {name, _doc}, acc -> max(byte_size(name), acc) end)
+
+ Enum.each(tasks, fn {name, doc} ->
+ shell_info("#{String.pad_trailing(name, max + 2)} # #{doc}")
+ end)
+ end
+
+ @spec list_subtasks_for_module(atom()) :: list({String.t(), String.t()})
+ def list_subtasks_for_module(module_name) do
+ Application.load(:mobilizon)
+ {:ok, modules} = :application.get_key(:mobilizon, :modules)
+ module_name = to_string(module_name)
+
+ modules
+ |> Enum.filter(fn module ->
+ String.starts_with?(to_string(module), to_string(module_name)) &&
+ to_string(module) != to_string(module_name)
+ end)
+ |> Enum.map(&format_module/1)
+ end
+
+ defp format_module(module) do
+ {format_name(to_string(module)), shortdoc(module)}
+ end
+
+ defp format_name("Elixir.Mix.Tasks.Mobilizon." <> task_name) do
+ String.downcase(task_name)
+ end
end
diff --git a/lib/mix/tasks/mobilizon/create_bot.ex b/lib/mix/tasks/mobilizon/create_bot.ex
index fce2b6166..2aea6166b 100644
--- a/lib/mix/tasks/mobilizon/create_bot.ex
+++ b/lib/mix/tasks/mobilizon/create_bot.ex
@@ -8,12 +8,13 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do
alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors.Bot
alias Mobilizon.Users.User
+ import Mix.Tasks.Mobilizon.Common
require Logger
@shortdoc "Create bot"
def run([email, name, summary, type, url]) do
- Mix.Task.run("app.start")
+ start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true),
actor <- Actors.register_bot(%{name: name, summary: summary}),
diff --git a/lib/mix/tasks/mobilizon/ecto.ex b/lib/mix/tasks/mobilizon/ecto.ex
new file mode 100644
index 000000000..5ce88e5e1
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/ecto.ex
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Mix.Tasks.Mobilizon.Ecto do
+ @moduledoc """
+ Provides tools for Ecto-related tasks (such as migrations)
+ """
+
+ @doc """
+ Ensures the given repository's migrations path exists on the file system.
+ """
+ @spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t()
+ def ensure_migrations_path(repo, opts) do
+ path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
+
+ path =
+ case Path.type(path) do
+ :relative ->
+ Path.join(Application.app_dir(:mobilizon), path)
+
+ :absolute ->
+ path
+ end
+
+ if not File.dir?(path) do
+ raise_missing_migrations(Path.relative_to_cwd(path), repo)
+ end
+
+ path
+ end
+
+ @doc """
+ Returns the private repository path relative to the source.
+ """
+ def source_repo_priv(repo) do
+ config = repo.config()
+ priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}"
+ Path.join(Application.app_dir(:mobilizon), priv)
+ end
+
+ defp raise_missing_migrations(path, repo) do
+ raise("""
+ Could not find migrations directory #{inspect(path)}
+ for repo #{inspect(repo)}.
+ This may be because you are in a new project and the
+ migration directory has not been created yet. Creating an
+ empty directory at the path above will fix this error.
+ If you expected existing migrations to be found, please
+ make sure your repository has been properly configured
+ and the configured path exists.
+ """)
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/ecto/migrate.ex b/lib/mix/tasks/mobilizon/ecto/migrate.ex
new file mode 100644
index 000000000..3e6c8812e
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/ecto/migrate.ex
@@ -0,0 +1,71 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Mobilizon.Ecto.Migrate do
+ use Mix.Task
+ import Mix.Tasks.Mobilizon.Common
+ alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask
+ require Logger
+
+ @shortdoc "Wrapper on `ecto.migrate` task."
+
+ @aliases [
+ n: :step,
+ v: :to
+ ]
+
+ @switches [
+ all: :boolean,
+ step: :integer,
+ to: :integer,
+ quiet: :boolean,
+ log_sql: :boolean,
+ strict_version_order: :boolean,
+ migrations_path: :string
+ ]
+
+ @repo Mobilizon.Storage.Repo
+
+ @moduledoc """
+ Changes `Logger` level to `:info` before start migration.
+ Changes level back when migration ends.
+
+ ## Start migration
+
+ mix mobilizon.ecto.migrate [OPTIONS]
+
+ Options:
+ - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Migrate.html
+ """
+
+ @impl true
+ def run(args \\ []) do
+ start_mobilizon()
+ {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+
+ if Application.get_env(:mobilizon, @repo)[:ssl] do
+ Application.ensure_all_started(:ssl)
+ end
+
+ opts =
+ if opts[:to] || opts[:step] || opts[:all],
+ do: opts,
+ else: Keyword.put(opts, :all, true)
+
+ opts =
+ if opts[:quiet],
+ do: Keyword.merge(opts, log: false, log_sql: false),
+ else: opts
+
+ path = EctoTask.ensure_migrations_path(@repo, opts)
+
+ level = Logger.level()
+ Logger.configure(level: :info)
+
+ {:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :up, opts))
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/ecto/rollback.ex b/lib/mix/tasks/mobilizon/ecto/rollback.ex
new file mode 100644
index 000000000..694e948da
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/ecto/rollback.ex
@@ -0,0 +1,74 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Mobilizon.Ecto.Rollback do
+ use Mix.Task
+ import Mix.Tasks.Mobilizon.Common
+ alias Mix.Tasks.Mobilizon.Ecto, as: EctoTask
+ require Logger
+ @shortdoc "Wrapper on `ecto.rollback` task"
+
+ @aliases [
+ n: :step,
+ v: :to
+ ]
+
+ @switches [
+ all: :boolean,
+ step: :integer,
+ to: :integer,
+ start: :boolean,
+ quiet: :boolean,
+ log_sql: :boolean,
+ migrations_path: :string
+ ]
+
+ @repo Mobilizon.Storage.Repo
+
+ @moduledoc """
+ Changes `Logger` level to `:info` before start rollback.
+ Changes level back when rollback ends.
+
+ ## Start rollback
+
+ mix mobilizon.ecto.rollback
+
+ Options:
+ - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Rollback.html
+ """
+
+ @impl true
+ def run(args \\ []) do
+ start_mobilizon()
+ {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+
+ if Application.get_env(:mobilizon, @repo)[:ssl] do
+ Application.ensure_all_started(:ssl)
+ end
+
+ opts =
+ if opts[:to] || opts[:step] || opts[:all],
+ do: opts,
+ else: Keyword.put(opts, :step, 1)
+
+ opts =
+ if opts[:quiet],
+ do: Keyword.merge(opts, log: false, log_sql: false),
+ else: opts
+
+ path = EctoTask.ensure_migrations_path(@repo, opts)
+
+ level = Logger.level()
+ Logger.configure(level: :info)
+
+ if Mobilizon.Config.get(:env) == :test do
+ Logger.info("Rollback succesfully")
+ else
+ {:ok, _, _} = Ecto.Migrator.with_repo(@repo, &Ecto.Migrator.run(&1, path, :down, opts))
+ end
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/groups.ex b/lib/mix/tasks/mobilizon/groups.ex
index 412b38fe5..3d3d3a6f9 100644
--- a/lib/mix/tasks/mobilizon/groups.ex
+++ b/lib/mix/tasks/mobilizon/groups.ex
@@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups do
use Mix.Task
alias Mix.Tasks
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Manages Mobilizon groups"
@impl Mix.Task
def run(_) do
- Mix.shell().info("\nAvailable tasks:")
+ shell_info("\nAvailable tasks:")
Tasks.Help.run(["--search", "mobilizon.groups."])
end
end
diff --git a/lib/mix/tasks/mobilizon/groups/refresh.ex b/lib/mix/tasks/mobilizon/groups/refresh.ex
index a88e0d22d..6e5caac57 100644
--- a/lib/mix/tasks/mobilizon/groups/refresh.ex
+++ b/lib/mix/tasks/mobilizon/groups/refresh.ex
@@ -6,12 +6,13 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Refresher
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Refresh a group private informations from an account member"
@impl Mix.Task
def run([group_url, on_behalf_of]) do
- Mix.Task.run("app.start")
+ start_mobilizon()
with %Actor{} = actor <- Actors.get_local_actor_by_name(on_behalf_of) do
res = Refresher.fetch_group(group_url, actor)
@@ -20,7 +21,7 @@ defmodule Mix.Tasks.Mobilizon.Groups.Refresh do
end
def run(_) do
- Mix.raise(
+ shell_error(
"mobilizon.groups.refresh requires a group URL and an actor username which is member of the group as arguments"
)
end
diff --git a/lib/mix/tasks/mobilizon/instance.ex b/lib/mix/tasks/mobilizon/instance.ex
index a833edcdf..a5bf2d648 100644
--- a/lib/mix/tasks/mobilizon/instance.ex
+++ b/lib/mix/tasks/mobilizon/instance.ex
@@ -29,7 +29,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
use Mix.Task
- alias Mix.Tasks.Mobilizon.Common
+ import Mix.Tasks.Mobilizon.Common
@preferred_cli_env "prod"
@@ -70,7 +70,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
if proceed? do
[domain, port | _] =
String.split(
- Common.get_option(
+ get_option(
options,
:domain,
"What domain will your instance use? (e.g mobilizon.org)"
@@ -79,25 +79,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do
) ++ [443]
name =
- Common.get_option(
+ get_option(
options,
:instance_name,
"What is the name of your instance? (e.g. Mobilizon)"
)
email =
- Common.get_option(
+ get_option(
options,
:admin_email,
"What's the address email will be send with?",
"noreply@#{domain}"
)
- dbhost =
- Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
+ dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
dbname =
- Common.get_option(
+ get_option(
options,
:dbname,
"What is the name of your database?",
@@ -105,7 +104,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
)
dbuser =
- Common.get_option(
+ get_option(
options,
:dbuser,
"What is the user used to connect to your database?",
@@ -113,7 +112,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
)
dbpass =
- Common.get_option(
+ get_option(
options,
:dbpass,
"What is the password used to connect to your database?",
@@ -122,7 +121,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
)
listen_port =
- Common.get_option(
+ get_option(
options,
:listen_port,
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
@@ -160,24 +159,24 @@ defmodule Mix.Tasks.Mobilizon.Instance do
database_password: dbpass
)
- Mix.shell().info("Writing config to #{config_path}.")
+ shell_info("Writing config to #{config_path}.")
File.write(config_path, result_config)
- Mix.shell().info("Writing #{psql_path}.")
+ shell_info("Writing #{psql_path}.")
File.write(psql_path, result_psql)
- Mix.shell().info(
+ shell_info(
"\n" <>
"""
To get started:
1. Check the contents of the generated files.
- 2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)} && rm #{
- Common.escape_sh_path(psql_path)
+ 2. Run `sudo -u postgres psql -f #{escape_sh_path(psql_path)} && rm #{
+ escape_sh_path(psql_path)
}`.
"""
)
else
- Mix.shell().error(
+ shell_error(
"The task would have overwritten the following files:\n" <>
(will_overwrite |> Enum.map(&"- #{&1}\n") |> Enum.join("")) <>
"Rerun with `-f/--force` to overwrite them."
diff --git a/lib/mix/tasks/mobilizon/move_participant_stats.ex b/lib/mix/tasks/mobilizon/move_participant_stats.ex
deleted file mode 100644
index 5c31be348..000000000
--- a/lib/mix/tasks/mobilizon/move_participant_stats.ex
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule Mix.Tasks.Mobilizon.MoveParticipantStats do
- @moduledoc """
- Temporary task to move participant stats in the events table
-
- This task will be removed in version 1.0.0-beta.3
- """
-
- use Mix.Task
-
- import Ecto.Query
-
- alias Mobilizon.Events
- alias Mobilizon.Events.Event
- alias Mobilizon.Events.ParticipantRole
- alias Mobilizon.Storage.Repo
-
- require Logger
-
- @shortdoc "Move participant stats to events table"
- def run([]) do
- Mix.Task.run("app.start")
-
- events =
- Event
- |> preload([e], :tags)
- |> Repo.all()
-
- nb_events = length(events)
-
- IO.puts(
- "\nStarting inserting participants stats into #{nb_events} events, this can take a while…\n"
- )
-
- insert_participants_stats_into_events(events, nb_events)
- end
-
- defp insert_participants_stats_into_events([%Event{url: url} = event | events], nb_events) do
- with roles <- ParticipantRole.__enum_map__(),
- counts <-
- Enum.reduce(roles, %{}, fn role, acc ->
- Map.put(acc, role, count_participants(event, role))
- end),
- {:ok, _} <-
- Events.update_event(event, %{
- participant_stats: counts
- }) do
- Logger.debug("Added participants stats to event #{url}")
- else
- {:error, res} ->
- Logger.error("Error while adding participants stats to event #{url} : #{inspect(res)}")
- end
-
- ProgressBar.render(nb_events - length(events), nb_events)
-
- insert_participants_stats_into_events(events, nb_events)
- end
-
- defp insert_participants_stats_into_events([], nb_events) do
- IO.puts("\nFinished inserting participant stats for #{nb_events} events!\n")
- end
-
- defp count_participants(%Event{id: event_id}, role) when is_atom(role) do
- event_id
- |> Events.count_participants_query()
- |> Events.filter_role(role)
- |> Repo.aggregate(:count, :id)
- end
-end
diff --git a/lib/mix/tasks/mobilizon/relay.ex b/lib/mix/tasks/mobilizon/relay.ex
index 4dc463a6a..03c612bc2 100644
--- a/lib/mix/tasks/mobilizon/relay.ex
+++ b/lib/mix/tasks/mobilizon/relay.ex
@@ -22,60 +22,19 @@ defmodule Mix.Tasks.Mobilizon.Relay do
use Mix.Task
- alias Mix.Tasks.Mobilizon.Common
-
- alias Mobilizon.Federation.ActivityPub.Relay
+ alias Mix.Tasks
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Manages remote relays"
- def run(["follow", target]) do
- Common.start_mobilizon()
- case Relay.follow(target) do
- {:ok, _activity, _follow} ->
- # put this task to sleep to allow the genserver to push out the messages
- :timer.sleep(500)
+ @impl Mix.Task
+ def run(_) do
+ shell_info("\nAvailable tasks:")
- {:error, e} ->
- IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}")
- end
- end
-
- def run(["unfollow", target]) do
- Common.start_mobilizon()
-
- case Relay.unfollow(target) do
- {:ok, _activity, _follow} ->
- # put this task to sleep to allow the genserver to push out the messages
- :timer.sleep(500)
-
- {:error, e} ->
- IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}")
- end
- end
-
- def run(["accept", target]) do
- Common.start_mobilizon()
-
- case Relay.accept(target) do
- {:ok, _activity} ->
- # put this task to sleep to allow the genserver to push out the messages
- :timer.sleep(500)
-
- {:error, e} ->
- IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}")
- end
- end
-
- def run(["refresh", target]) do
- Common.start_mobilizon()
- IO.puts("Refreshing #{target}, this can take a while.")
-
- case Relay.refresh(target) do
- :ok ->
- IO.puts("Refreshed #{target}")
-
- err ->
- IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}")
+ if mix_shell?() do
+ Tasks.Help.run(["--search", "mobilizon.relay."])
+ else
+ show_subtasks_for_module(__MODULE__)
end
end
end
diff --git a/lib/mix/tasks/mobilizon/relay/accept.ex b/lib/mix/tasks/mobilizon/relay/accept.ex
new file mode 100644
index 000000000..88d594ff8
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/relay/accept.ex
@@ -0,0 +1,28 @@
+defmodule Mix.Tasks.Mobilizon.Relay.Accept do
+ @moduledoc """
+ Task to accept an instance follow request
+ """
+ use Mix.Task
+ alias Mobilizon.Federation.ActivityPub.Relay
+ import Mix.Tasks.Mobilizon.Common
+
+ @shortdoc "Accept an instance follow request"
+
+ @impl Mix.Task
+ def run([target]) do
+ start_mobilizon()
+
+ case Relay.accept(target) do
+ {:ok, _activity} ->
+ # put this task to sleep to allow the genserver to push out the messages
+ :timer.sleep(500)
+
+ {:error, e} ->
+ IO.puts(:stderr, "Error while accept #{target} follow: #{inspect(e)}")
+ end
+ end
+
+ def run(_) do
+ shell_error("mobilizon.relay.accept requires an instance hostname as arguments")
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/relay/follow.ex b/lib/mix/tasks/mobilizon/relay/follow.ex
new file mode 100644
index 000000000..43c29f456
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/relay/follow.ex
@@ -0,0 +1,28 @@
+defmodule Mix.Tasks.Mobilizon.Relay.Follow do
+ @moduledoc """
+ Task to follow an instance
+ """
+ use Mix.Task
+ alias Mobilizon.Federation.ActivityPub.Relay
+ import Mix.Tasks.Mobilizon.Common
+
+ @shortdoc "Follow an instance"
+
+ @impl Mix.Task
+ def run([target]) do
+ start_mobilizon()
+
+ case Relay.follow(target) do
+ {:ok, _activity, _follow} ->
+ # put this task to sleep to allow the genserver to push out the messages
+ :timer.sleep(500)
+
+ {:error, e} ->
+ IO.puts(:stderr, "Error while following #{target}: #{inspect(e)}")
+ end
+ end
+
+ def run(_) do
+ shell_error("mobilizon.relay.follow requires an instance hostname as arguments")
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/relay/refresh.ex b/lib/mix/tasks/mobilizon/relay/refresh.ex
new file mode 100644
index 000000000..f788009b4
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/relay/refresh.ex
@@ -0,0 +1,28 @@
+defmodule Mix.Tasks.Mobilizon.Relay.Refresh do
+ @moduledoc """
+ Task to refresh an instance details
+ """
+ use Mix.Task
+ alias Mobilizon.Federation.ActivityPub.Relay
+ import Mix.Tasks.Mobilizon.Common
+
+ @shortdoc "Refresh an instance informations and crawl their outbox"
+
+ @impl Mix.Task
+ def run([target]) do
+ start_mobilizon()
+ IO.puts("Refreshing #{target}, this can take a while.")
+
+ case Relay.refresh(target) do
+ :ok ->
+ IO.puts("Refreshed #{target}")
+
+ err ->
+ IO.puts(:stderr, "Error while refreshing #{target}: #{inspect(err)}")
+ end
+ end
+
+ def run(_) do
+ shell_error("mobilizon.relay.refresh requires an instance hostname as arguments")
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/relay/unfollow.ex b/lib/mix/tasks/mobilizon/relay/unfollow.ex
new file mode 100644
index 000000000..c368c3fb5
--- /dev/null
+++ b/lib/mix/tasks/mobilizon/relay/unfollow.ex
@@ -0,0 +1,28 @@
+defmodule Mix.Tasks.Mobilizon.Relay.Unfollow do
+ @moduledoc """
+ Task to unfollow an instance
+ """
+ use Mix.Task
+ alias Mobilizon.Federation.ActivityPub.Relay
+ import Mix.Tasks.Mobilizon.Common
+
+ @shortdoc "Unfollow an instance"
+
+ @impl Mix.Task
+ def run([target]) do
+ start_mobilizon()
+
+ case Relay.unfollow(target) do
+ {:ok, _activity, _follow} ->
+ # put this task to sleep to allow the genserver to push out the messages
+ :timer.sleep(500)
+
+ {:error, e} ->
+ IO.puts(:stderr, "Error while unfollowing #{target}: #{inspect(e)}")
+ end
+ end
+
+ def run(_) do
+ shell_error("mobilizon.relay.unfollow requires an instance hostname as arguments")
+ end
+end
diff --git a/lib/mix/tasks/mobilizon/setup_search.ex b/lib/mix/tasks/mobilizon/setup_search.ex
deleted file mode 100644
index 4633a2a76..000000000
--- a/lib/mix/tasks/mobilizon/setup_search.ex
+++ /dev/null
@@ -1,50 +0,0 @@
-defmodule Mix.Tasks.Mobilizon.SetupSearch do
- @moduledoc """
- Temporary task to insert search data from existing events
-
- This task will be removed in version 1.0.0-beta.3
- """
-
- use Mix.Task
-
- import Ecto.Query
-
- alias Mobilizon.Events.Event
- alias Mobilizon.Service.Workers
- alias Mobilizon.Storage.Repo
-
- require Logger
-
- @shortdoc "Insert search data"
- def run([]) do
- Mix.Task.run("app.start")
-
- events =
- Event
- |> preload([e], :tags)
- |> Repo.all()
-
- nb_events = length(events)
-
- IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n")
- insert_search_event(events, nb_events)
- end
-
- defp insert_search_event([%Event{url: url} = event | events], nb_events) do
- case Workers.BuildSearch.insert_search_event(event) do
- {:ok, _} ->
- Logger.debug("Added event #{url} to the search")
-
- {:error, res} ->
- Logger.error("Error while adding event #{url} to the search: #{inspect(res)}")
- end
-
- ProgressBar.render(nb_events - length(events), nb_events)
-
- insert_search_event(events, nb_events)
- end
-
- defp insert_search_event([], nb_events) do
- IO.puts("\nFinished setting up search for #{nb_events} events!\n")
- end
-end
diff --git a/lib/mix/tasks/mobilizon/site_map.ex b/lib/mix/tasks/mobilizon/site_map.ex
index ef4162892..d35c94990 100644
--- a/lib/mix/tasks/mobilizon/site_map.ex
+++ b/lib/mix/tasks/mobilizon/site_map.ex
@@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do
"""
use Mix.Task
- alias Mix.Tasks.Mobilizon.Common
+ import Mix.Tasks.Mobilizon.Common
alias Mobilizon.Service.SiteMap
alias Mobilizon.Web.Endpoint
@@ -12,10 +12,10 @@ defmodule Mix.Tasks.Mobilizon.SiteMap do
@shortdoc "Generates a new Sitemap"
def run(["generate"]) do
- Common.start_mobilizon()
+ start_mobilizon()
with {:ok, :ok} <- SiteMap.generate_sitemap() do
- Mix.shell().info("Sitemap saved to #{Endpoint.url()}/sitemap.xml")
+ shell_info("Sitemap saved to #{Endpoint.url()}/sitemap.xml")
end
end
end
diff --git a/lib/mix/tasks/mobilizon/toot.ex b/lib/mix/tasks/mobilizon/toot.ex
deleted file mode 100644
index 3473d8b56..000000000
--- a/lib/mix/tasks/mobilizon/toot.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-defmodule Mix.Tasks.Mobilizon.Toot do
- @moduledoc """
- Creates a bot from a source.
- """
-
- use Mix.Task
-
- alias Mobilizon.Actors
- alias Mobilizon.Actors.Actor
-
- alias Mobilizon.GraphQL.API.Comments
-
- require Logger
-
- @shortdoc "Toot to an user"
- def run([from, text]) do
- Mix.Task.run("app.start")
-
- with {:local_actor, %Actor{} = actor} <- {:local_actor, Actors.get_local_actor_by_name(from)},
- {:ok, _, _} <- Comments.create_comment(%{actor: actor, text: text}) do
- Mix.shell().info("Tooted")
- else
- {:local_actor, _, _} ->
- Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
-
- _ ->
- Mix.shell().error("Failed to toot.")
- end
- end
-end
diff --git a/lib/mix/tasks/mobilizon/users.ex b/lib/mix/tasks/mobilizon/users.ex
index e61a4e77a..aa3386b7f 100644
--- a/lib/mix/tasks/mobilizon/users.ex
+++ b/lib/mix/tasks/mobilizon/users.ex
@@ -6,12 +6,18 @@ defmodule Mix.Tasks.Mobilizon.Users do
use Mix.Task
alias Mix.Tasks
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Manages Mobilizon users"
@impl Mix.Task
def run(_) do
- Mix.shell().info("\nAvailable tasks:")
- Tasks.Help.run(["--search", "mobilizon.users."])
+ shell_info("\nAvailable tasks:")
+
+ if mix_shell?() do
+ Tasks.Help.run(["--search", "mobilizon.users."])
+ else
+ show_subtasks_for_module(__MODULE__)
+ end
end
end
diff --git a/lib/mix/tasks/mobilizon/users/delete.ex b/lib/mix/tasks/mobilizon/users/delete.ex
index d769601a0..c72cd97a8 100644
--- a/lib/mix/tasks/mobilizon/users/delete.ex
+++ b/lib/mix/tasks/mobilizon/users/delete.ex
@@ -5,6 +5,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do
use Mix.Task
alias Mobilizon.Users
alias Mobilizon.Users.User
+ import Mix.Tasks.Mobilizon.Common
@shortdoc "Deletes a Mobilizon user"
@@ -26,25 +27,25 @@ defmodule Mix.Tasks.Mobilizon.Users.Delete do
assume_yes? = Keyword.get(options, :assume_yes, false)
keep_email? = Keyword.get(options, :keep_email, false)
- Mix.Task.run("app.start")
+ start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
- true <- assume_yes? or Mix.shell().yes?("Continue with deleting user #{user.email}?"),
+ true <- assume_yes? or shell_yes?("Continue with deleting user #{user.email}?"),
{:ok, %User{} = user} <-
Users.delete_user(user, reserve_email: keep_email?) do
- Mix.shell().info("""
+ shell_info("""
The user #{user.email} has been deleted
""")
else
{:error, :user_not_found} ->
- Mix.raise("Error: No such user")
+ shell_error("Error: No such user")
_ ->
- Mix.raise("User has not been deleted.")
+ shell_error("User has not been deleted.")
end
end
def run(_) do
- Mix.raise("mobilizon.users.delete requires an email as argument")
+ shell_error("mobilizon.users.delete requires an email as argument")
end
end
diff --git a/lib/mix/tasks/mobilizon/users/modify.ex b/lib/mix/tasks/mobilizon/users/modify.ex
index 66e0b26ee..dd8a4181c 100644
--- a/lib/mix/tasks/mobilizon/users/modify.ex
+++ b/lib/mix/tasks/mobilizon/users/modify.ex
@@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
Task to modify an existing Mobilizon user
"""
use Mix.Task
+ import Mix.Tasks.Mobilizon.Common
alias Mobilizon.Users
alias Mobilizon.Users.User
@@ -31,10 +32,10 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
new_email = Keyword.get(options, :email)
if disable? && enable? do
- Mix.raise("Can't use both --enabled and --disable options at the same time.")
+ shell_error("Can't use both --enabled and --disable options at the same time.")
end
- Mix.Task.run("app.start")
+ start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
attrs <- %{},
@@ -53,7 +54,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
),
{:makes_changes, true} <- {:makes_changes, attrs != %{}},
{:ok, %User{} = user} <- Users.update_user(user, attrs) do
- Mix.shell().info("""
+ shell_info("""
An user has been modified with the following information:
- email: #{user.email}
- Role: #{user.role}
@@ -61,23 +62,23 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
""")
else
{:makes_changes, false} ->
- Mix.shell().info("No change has been made")
+ shell_info("No change has been made")
{:error, :user_not_found} ->
- Mix.raise("Error: No such user")
+ shell_error("Error: No such user")
{:error, %Ecto.Changeset{errors: errors}} ->
- Mix.shell().error(inspect(errors))
- Mix.raise("User has not been modified because of the above reason.")
+ shell_error(inspect(errors))
+ shell_error("User has not been modified because of the above reason.")
err ->
- Mix.shell().error(inspect(err))
- Mix.raise("User has not been modified because of an unknown reason.")
+ shell_error(inspect(err))
+ shell_error("User has not been modified because of an unknown reason.")
end
end
def run(_) do
- Mix.raise("mobilizon.users.new requires an email as argument")
+ shell_error("mobilizon.users.new requires an email as argument")
end
@spec process_new_value(map(), atom(), any(), any()) :: map()
diff --git a/lib/mix/tasks/mobilizon/users/new.ex b/lib/mix/tasks/mobilizon/users/new.ex
index d0802b329..453762798 100644
--- a/lib/mix/tasks/mobilizon/users/new.ex
+++ b/lib/mix/tasks/mobilizon/users/new.ex
@@ -3,6 +3,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
Task to create a new user
"""
use Mix.Task
+ import Mix.Tasks.Mobilizon.Common
alias Mobilizon.Users
alias Mobilizon.Users.User
@@ -40,7 +41,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
:crypto.strong_rand_bytes(16) |> Base.encode64() |> binary_part(0, 16)
)
- Mix.Task.run("app.start")
+ start_mobilizon()
case Users.register(%{
email: email,
@@ -51,7 +52,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
confirmation_token: nil
}) do
{:ok, %User{} = user} ->
- Mix.shell().info("""
+ shell_info("""
An user has been created with the following information:
- email: #{user.email}
- password: #{password}
@@ -60,16 +61,16 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
""")
{:error, %Ecto.Changeset{errors: errors}} ->
- Mix.shell().error(inspect(errors))
- Mix.raise("User has not been created because of the above reason.")
+ shell_error(inspect(errors))
+ shell_error("User has not been created because of the above reason.")
err ->
- Mix.shell().error(inspect(err))
- Mix.raise("User has not been created because of an unknown reason.")
+ shell_error(inspect(err))
+ shell_error("User has not been created because of an unknown reason.")
end
end
def run(_) do
- Mix.raise("mobilizon.users.new requires an email as argument")
+ shell_error("mobilizon.users.new requires an email as argument")
end
end
diff --git a/lib/mix/tasks/mobilizon/users/show.ex b/lib/mix/tasks/mobilizon/users/show.ex
index 81864bfe6..3c33aaf52 100644
--- a/lib/mix/tasks/mobilizon/users/show.ex
+++ b/lib/mix/tasks/mobilizon/users/show.ex
@@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
"""
use Mix.Task
-
+ import Mix.Tasks.Mobilizon.Common
alias Mobilizon.Actors.Actor
alias Mobilizon.Users
alias Mobilizon.Users.User
@@ -13,11 +13,11 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
@impl Mix.Task
def run([email]) do
- Mix.Task.run("app.start")
+ start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
actors <- Users.get_actors_for_user(user) do
- Mix.shell().info("""
+ shell_info("""
Informations for the user #{user.email}:
- Activated: #{user.confirmed_at}
- Disabled: #{user.disabled}
@@ -26,12 +26,12 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
""")
else
{:error, :user_not_found} ->
- Mix.raise("Error: No such user")
+ shell_error("Error: No such user")
end
end
def run(_) do
- Mix.raise("mobilizon.users.show requires an email as argument")
+ shell_error("mobilizon.users.show requires an email as argument")
end
defp display_actors([]), do: ""
diff --git a/lib/mobilizon/cli.ex b/lib/mobilizon/cli.ex
index 969c125f0..91d105d3d 100644
--- a/lib/mobilizon/cli.ex
+++ b/lib/mobilizon/cli.ex
@@ -1,11 +1,53 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Mobilizon.CLI do
- @app :mobilizon
+ @moduledoc """
+ CLI wrapper for releases
+ """
+ alias Mix.Tasks.Mobilizon.Ecto.{Migrate, Rollback}
- def migrate do
- Application.load(@app)
+ def run(args) do
+ [task | args] = String.split(args)
- for repo <- Application.fetch_env!(@app, :ecto_repos) do
- {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
+ case task do
+ "migrate" -> migrate(args)
+ "rollback" -> rollback(args)
+ task -> mix_task(task, args)
end
end
+
+ defp mix_task(task, args) do
+ Application.load(:mobilizon)
+ {:ok, modules} = :application.get_key(:mobilizon, :modules)
+
+ module =
+ Enum.find(modules, fn module ->
+ module = Module.split(module)
+
+ case module do
+ ["Mix", "Tasks", "Mobilizon" | rest] ->
+ String.downcase(Enum.join(rest, ".")) == task
+
+ _ ->
+ false
+ end
+ end)
+
+ if module do
+ module.run(args)
+ else
+ IO.puts("The task #{task} does not exist")
+ end
+ end
+
+ def migrate(args) do
+ Migrate.run(args)
+ end
+
+ def rollback(args) do
+ Rollback.run(args)
+ end
end
diff --git a/test/tasks/actors_test.exs b/test/tasks/actors_test.exs
index a67c69468..934dd33e0 100644
--- a/test/tasks/actors_test.exs
+++ b/test/tasks/actors_test.exs
@@ -48,7 +48,9 @@ defmodule Mix.Tasks.Mobilizon.ActorsTest do
end
test "show non-existing actor" do
- assert_raise Mix.Error, "Error: No such actor", fn -> Show.run([@username]) end
+ Show.run([@username])
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Error: No such actor"
end
end
end
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index 5e64dfdd5..87f197718 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -9,6 +9,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Follower}
+ alias Mix.Tasks.Mobilizon.Relay.{Follow, Unfollow}
alias Mobilizon.Federation.ActivityPub.Relay
@@ -17,7 +18,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
use_cassette "relay/fetch_relay_follow" do
target_instance = "mobilizon1.com"
- Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
+ Follow.run([target_instance])
local_actor = Relay.get_actor()
assert local_actor.url =~ "/relay"
@@ -35,7 +36,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
use_cassette "relay/fetch_relay_unfollow" do
target_instance = "mobilizon1.com"
- Mix.Tasks.Mobilizon.Relay.run(["follow", target_instance])
+ Follow.run([target_instance])
%Actor{} = local_actor = Relay.get_actor()
@@ -44,7 +45,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
assert %Follower{} = Actors.is_following(local_actor, target_actor)
- Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])
+ Unfollow.run([target_instance])
refute Actors.is_following(local_actor, target_actor)
end
diff --git a/test/tasks/users_test.exs b/test/tasks/users_test.exs
index 10d88ba59..4c9cbc625 100644
--- a/test/tasks/users_test.exs
+++ b/test/tasks/users_test.exs
@@ -42,9 +42,15 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
test "create with already used email" do
insert(:user, email: @email)
- assert_raise Mix.Error, "User has not been created because of the above reason.", fn ->
- New.run([@email])
- end
+ New.run([@email])
+ # Debug message
+ assert_received {:mix_shell, :error, [message]}
+
+ assert message =~
+ "[email: {\"This email is already used.\", [constraint: :unique, constraint_name: \"users_email_index\"]}]"
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "User has not been created because of the above reason."
end
end
@@ -62,7 +68,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
end
test "delete non-existing user" do
- assert_raise Mix.Error, "Error: No such user", fn -> Delete.run([@email, "-y"]) end
+ Delete.run([@email, "-y"])
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Error: No such user"
end
end
@@ -87,7 +95,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
end
test "show non-existing user" do
- assert_raise Mix.Error, "Error: No such user", fn -> Show.run([@email]) end
+ Show.run([@email])
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Error: No such user"
end
end
@@ -160,11 +170,9 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
end
test "enable and disable at the same time" do
- assert_raise Mix.Error,
- "Can't use both --enabled and --disable options at the same time.",
- fn ->
- Modify.run([@email, "--disable", "--enable"])
- end
+ Modify.run([@email, "--disable", "--enable"])
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "Can't use both --enabled and --disable options at the same time."
end
end
end