Merge branch 'develop' into admin-be
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
index 2f43f018b21c47a8848fc2e3e7ae6beb72290881..d5230f9aa5cbb7d3d2744e25c83444803908c55e 100644 (file)
@@ -4,15 +4,20 @@
 
 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
   alias Pleroma.Activity
+  alias Pleroma.ConfigDB
   alias Pleroma.ModerationLog
   alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.ReportNote
   alias Pleroma.User
   alias Pleroma.UserInviteToken
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Relay
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.AdminAPI.AccountView
-  alias Pleroma.Web.AdminAPI.Config
   alias Pleroma.Web.AdminAPI.ConfigView
   alias Pleroma.Web.AdminAPI.ModerationLogView
   alias Pleroma.Web.AdminAPI.Report
@@ -23,26 +28,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.Router
 
-  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
-
   require Logger
 
+  @descriptions_json Pleroma.Docs.JSON.compile()
+  @users_page_size 50
+
   plug(
     OAuthScopesPlug,
-    %{scopes: ["read:accounts"]}
-    when action in [:list_users, :user_show, :right_get, :invites]
+    %{scopes: ["read:accounts"], admin: true}
+    when action in [:list_users, :user_show, :right_get]
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["write:accounts"]}
+    %{scopes: ["write:accounts"], admin: true}
     when action in [
-           :get_invite_token,
-           :revoke_invite,
-           :email_invite,
            :get_password_reset,
-           :user_follow,
-           :user_unfollow,
            :user_delete,
            :users_create,
            :user_toggle_activation,
@@ -55,41 +56,55 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
          ]
   )
 
+  plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
+
   plug(
     OAuthScopesPlug,
-    %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
+    %{scopes: ["write:invites"], admin: true}
+    when action in [:create_invite_token, :revoke_invite, :email_invite]
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["write:reports"]}
-    when action in [:report_update_state, :report_respond]
+    %{scopes: ["write:follows"], admin: true}
+    when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["read:statuses"]} when action == :list_user_statuses
+    %{scopes: ["read:reports"], admin: true}
+    when action in [:list_reports, :report_show]
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["write:statuses"]}
-    when action in [:status_update, :status_delete]
+    %{scopes: ["write:reports"], admin: true}
+    when action in [:reports_update]
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["read"]}
-    when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
+    %{scopes: ["read:statuses"], admin: true}
+    when action == :list_user_statuses
   )
 
   plug(
     OAuthScopesPlug,
-    %{scopes: ["write"]}
-    when action in [:relay_follow, :relay_unfollow, :config_update]
+    %{scopes: ["write:statuses"], admin: true}
+    when action in [:status_update, :status_delete]
   )
 
-  @users_page_size 50
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["read"], admin: true}
+    when action in [:config_show, :migrate_from_db, :list_log]
+  )
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write"], admin: true}
+    when action == :config_update
+  )
 
   action_fallback(:errors)
 
@@ -237,7 +252,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       })
 
     conn
-    |> put_view(StatusView)
+    |> put_view(Pleroma.Web.AdminAPI.StatusView)
     |> render("index.json", %{activities: activities, as: :activity})
   end
 
@@ -349,7 +364,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     }
 
     with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
-         {:ok, users, count} <- filter_relay_user(users, count),
+         {:ok, users, count} <- filter_service_users(users, count),
          do:
            conn
            |> json(
@@ -361,15 +376,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            )
   end
 
-  defp filter_relay_user(users, count) do
-    filtered_users = Enum.reject(users, &relay_user?/1)
-    count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
+  defp filter_service_users(users, count) do
+    filtered_users = Enum.reject(users, &service_user?/1)
+    count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
 
     {:ok, filtered_users, count}
   end
 
-  defp relay_user?(user) do
-    user.ap_id == Relay.relay_ap_id()
+  defp service_user?(user) do
+    String.match?(user.ap_id, ~r/.*\/relay$/) or
+      String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
   end
 
   @filters ~w(local external active deactivated is_admin is_moderator)
@@ -625,7 +641,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
     users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
 
-    Enum.map(users, &User.force_password_reset_async/1)
+    Enum.each(users, &User.force_password_reset_async/1)
 
     ModerationLog.insert_log(%{
       actor: admin,
@@ -639,21 +655,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   def list_reports(conn, params) do
     {page, page_size} = page_params(params)
 
-    params =
-      params
-      |> Map.put("type", "Flag")
-      |> Map.put("skip_preload", true)
-      |> Map.put("total", true)
-      |> Map.put("limit", page_size)
-      |> Map.put("offset", (page - 1) * page_size)
-
-    reports = ActivityPub.fetch_activities([], params, :offset)
+    reports = Utils.get_reports(params, page, page_size)
 
     conn
     |> put_view(ReportView)
     |> render("index.json", %{reports: reports})
   end
 
+  def list_grouped_reports(conn, _params) do
+    statuses = Utils.get_reported_activities()
+
+    conn
+    |> put_view(ReportView)
+    |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
+  end
+
   def report_show(conn, %{"id" => id}) do
     with %Activity{} = report <- Activity.get_by_id(id) do
       conn
@@ -664,46 +680,62 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
-    with {:ok, report} <- CommonAPI.update_report_state(id, state) do
-      ModerationLog.insert_log(%{
-        action: "report_update",
-        actor: admin,
-        subject: report
-      })
+  def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
+    result =
+      reports
+      |> Enum.map(fn report ->
+        with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
+          ModerationLog.insert_log(%{
+            action: "report_update",
+            actor: admin,
+            subject: activity
+          })
+
+          activity
+        else
+          {:error, message} -> %{id: report["id"], error: message}
+        end
+      end)
 
-      conn
-      |> put_view(ReportView)
-      |> render("show.json", Report.extract_report_info(report))
+    case Enum.any?(result, &Map.has_key?(&1, :error)) do
+      true -> json_response(conn, :bad_request, result)
+      false -> json_response(conn, :no_content, "")
     end
   end
 
-  def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
-    with false <- is_nil(params["status"]),
-         %Activity{} <- Activity.get_by_id(id) do
-      params =
-        params
-        |> Map.put("in_reply_to_status_id", id)
-        |> Map.put("visibility", "direct")
+  def report_notes_create(%{assigns: %{user: user}} = conn, %{
+        "id" => report_id,
+        "content" => content
+      }) do
+    with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
+      ModerationLog.insert_log(%{
+        action: "report_note",
+        actor: user,
+        subject: Activity.get_by_id(report_id),
+        text: content
+      })
 
-      {:ok, activity} = CommonAPI.post(user, params)
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
 
+  def report_notes_delete(%{assigns: %{user: user}} = conn, %{
+        "id" => note_id,
+        "report_id" => report_id
+      }) do
+    with {:ok, note} <- ReportNote.destroy(note_id) do
       ModerationLog.insert_log(%{
-        action: "report_response",
+        action: "report_note_delete",
         actor: user,
-        subject: activity,
-        text: params["status"]
+        subject: Activity.get_by_id(report_id),
+        text: note.content
       })
 
-      conn
-      |> put_view(StatusView)
-      |> render("show.json", %{activity: activity})
+      json_response(conn, :no_content, "")
     else
-      true ->
-        {:param_cast, nil}
-
-      nil ->
-        {:error, :not_found}
+      _ -> json_response(conn, :bad_request, "")
     end
   end
 
@@ -755,49 +787,132 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> render("index.json", %{log: log})
   end
 
-  def migrate_to_db(conn, _params) do
-    Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
-    json(conn, %{})
+  def config_descriptions(conn, _params) do
+    conn
+    |> Plug.Conn.put_resp_content_type("application/json")
+    |> Plug.Conn.send_resp(200, @descriptions_json)
   end
 
   def migrate_from_db(conn, _params) do
-    Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
-    json(conn, %{})
+    with :ok <- configurable_from_database(conn) do
+      Mix.Tasks.Pleroma.Config.run([
+        "migrate_from_db",
+        "--env",
+        to_string(Pleroma.Config.get(:env)),
+        "-d"
+      ])
+
+      json(conn, %{})
+    end
+  end
+
+  def config_show(conn, %{"only_db" => true}) do
+    with :ok <- configurable_from_database(conn) do
+      configs = Pleroma.Repo.all(ConfigDB)
+
+      if configs == [] do
+        errors(
+          conn,
+          {:error, "To use configuration from database migrate your settings to database."}
+        )
+      else
+        conn
+        |> put_view(ConfigView)
+        |> render("index.json", %{configs: configs})
+      end
+    end
   end
 
   def config_show(conn, _params) do
-    configs = Pleroma.Repo.all(Config)
+    with :ok <- configurable_from_database(conn) do
+      configs = ConfigDB.get_all_as_keyword()
+
+      if configs == [] do
+        errors(
+          conn,
+          {:error, "To use configuration from database migrate your settings to database."}
+        )
+      else
+        merged =
+          Pleroma.Config.Holder.config()
+          |> ConfigDB.merge(configs)
+          |> Enum.map(fn {group, values} ->
+            Enum.map(values, fn {key, value} ->
+              db =
+                if configs[group][key] do
+                  ConfigDB.get_db_keys(configs[group][key], key)
+                end
+
+              db_value = configs[group][key]
+
+              merged_value =
+                if !is_nil(db_value) and Keyword.keyword?(db_value) and
+                     ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
+                  ConfigDB.deep_merge(group, key, value, db_value)
+                else
+                  value
+                end
+
+              setting = %{
+                group: ConfigDB.convert(group),
+                key: ConfigDB.convert(key),
+                value: ConfigDB.convert(merged_value)
+              }
+
+              if db, do: Map.put(setting, :db, db), else: setting
+            end)
+          end)
+          |> List.flatten()
 
-    conn
-    |> put_view(ConfigView)
-    |> render("index.json", %{configs: configs})
+        json(conn, %{configs: merged})
+      end
+    end
   end
 
   def config_update(conn, %{"configs" => configs}) do
-    updated =
-      if Pleroma.Config.get([:instance, :dynamic_configuration]) do
-        updated =
-          Enum.map(configs, fn
-            %{"group" => group, "key" => key, "delete" => "true"} = params ->
-              {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
+    with :ok <- configurable_from_database(conn) do
+      updated =
+        Enum.map(configs, fn
+          %{"group" => group, "key" => key, "delete" => true} = params ->
+            with {:ok, config} <-
+                   ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do
               config
+            end
 
-            %{"group" => group, "key" => key, "value" => value} ->
-              {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
+          %{"group" => group, "key" => key, "value" => value} ->
+            with {:ok, config} <-
+                   ConfigDB.update_or_create(%{group: group, key: key, value: value}) do
               config
-          end)
-          |> Enum.reject(&is_nil(&1))
+            end
+        end)
+        |> Enum.reject(&is_nil(&1))
+        |> Enum.map(fn config ->
+          Map.put(config, :db, ConfigDB.get_db_keys(config))
+        end)
 
-        Pleroma.Config.TransferTask.load_and_update_env()
-        Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
-        updated
-      else
-        []
-      end
+      Pleroma.Config.TransferTask.load_and_update_env()
 
-    conn
-    |> put_view(ConfigView)
-    |> render("index.json", %{configs: updated})
+      Mix.Tasks.Pleroma.Config.run([
+        "migrate_from_db",
+        "--env",
+        to_string(Pleroma.Config.get(:env))
+      ])
+
+      conn
+      |> put_view(ConfigView)
+      |> render("index.json", %{configs: updated})
+    end
+  end
+
+  defp configurable_from_database(conn) do
+    if Pleroma.Config.get(:configurable_from_database) do
+      :ok
+    else
+      errors(
+        conn,
+        {:error, "To use this endpoint you need to enable configuration from database."}
+      )
+    end
   end
 
   def reload_emoji(conn, _params) do
@@ -806,6 +921,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     conn |> json("ok")
   end
 
+  def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+    User.toggle_confirmation(users)
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: users,
+      action: "confirm_email"
+    })
+
+    conn |> json("")
+  end
+
+  def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+    User.try_send_confirmation_email(users)
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: users,
+      action: "resend_confirmation_email"
+    })
+
+    conn |> json("")
+  end
+
   def errors(conn, {:error, :not_found}) do
     conn
     |> put_status(:not_found)