Move report actions to AdminAPI.ReportController
authorEgor Kislitsyn <egor@kislitsyn.com>
Thu, 21 May 2020 15:43:56 +0000 (19:43 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 3 Jun 2020 15:17:04 +0000 (19:17 +0400)
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/admin_api/controllers/report_controller.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
test/web/admin_api/controllers/admin_api_controller_test.exs
test/web/admin_api/controllers/report_controller_test.exs [new file with mode: 0644]

index cc93fb5098a0a2fb14cd5f127a9edc0dcf8b6c96..467d05375e3e98c53510f5c492bfdddb69b22d07 100644 (file)
@@ -7,28 +7,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   import Pleroma.Web.ControllerHelper, only: [json_response: 3]
 
-  alias Pleroma.Activity
   alias Pleroma.Config
   alias Pleroma.ConfigDB
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
   alias Pleroma.Plugs.OAuthScopesPlug
-  alias Pleroma.ReportNote
   alias Pleroma.Stats
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Relay
-  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.AdminAPI
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.AdminAPI.ConfigView
   alias Pleroma.Web.AdminAPI.ModerationLogView
-  alias Pleroma.Web.AdminAPI.Report
-  alias Pleroma.Web.AdminAPI.ReportView
   alias Pleroma.Web.AdminAPI.Search
-  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router
 
@@ -71,18 +65,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
   )
 
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["read:reports"], admin: true}
-    when action in [:list_reports, :report_show]
-  )
-
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:reports"], admin: true}
-    when action in [:reports_update, :report_notes_create, :report_notes_delete]
-  )
-
   plug(
     OAuthScopesPlug,
     %{scopes: ["read:statuses"], admin: true}
@@ -645,85 +627,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def list_reports(conn, params) do
-    {page, page_size} = page_params(params)
-
-    reports = Utils.get_reports(params, page, page_size)
-
-    conn
-    |> put_view(ReportView)
-    |> render("index.json", %{reports: reports})
-  end
-
-  def report_show(conn, %{"id" => id}) do
-    with %Activity{} = report <- Activity.get_by_id(id) do
-      conn
-      |> put_view(ReportView)
-      |> render("show.json", Report.extract_report_info(report))
-    else
-      _ -> {:error, :not_found}
-    end
-  end
-
-  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)
-
-    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_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
-      })
-
-      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_note_delete",
-        actor: user,
-        subject: Activity.get_by_id(report_id),
-        text: note.content
-      })
-
-      json_response(conn, :no_content, "")
-    else
-      _ -> json_response(conn, :bad_request, "")
-    end
-  end
-
   def list_log(conn, params) do
     {page, page_size} = page_params(params)
 
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
new file mode 100644 (file)
index 0000000..23f0174
--- /dev/null
@@ -0,0 +1,129 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportController do
+  use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+  alias Pleroma.Activity
+  alias Pleroma.ModerationLog
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.ReportNote
+  alias Pleroma.Web.ActivityPub.Utils
+  alias Pleroma.Web.AdminAPI
+  alias Pleroma.Web.AdminAPI.Report
+  alias Pleroma.Web.CommonAPI
+
+  require Logger
+
+  @users_page_size 50
+
+  plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show])
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write:reports"], admin: true}
+    when action in [:update, :notes_create, :notes_delete]
+  )
+
+  action_fallback(AdminAPI.FallbackController)
+
+  def index(conn, params) do
+    {page, page_size} = page_params(params)
+
+    reports = Utils.get_reports(params, page, page_size)
+
+    render(conn, "index.json", reports: reports)
+  end
+
+  def show(conn, %{"id" => id}) do
+    with %Activity{} = report <- Activity.get_by_id(id) do
+      render(conn, "show.json", Report.extract_report_info(report))
+    else
+      _ -> {:error, :not_found}
+    end
+  end
+
+  def 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)
+
+    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 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
+      })
+
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+
+  def 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_note_delete",
+        actor: user,
+        subject: Activity.get_by_id(report_id),
+        text: note.content
+      })
+
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+
+  defp page_params(params) do
+    {get_page(params["page"]), get_page_size(params["page_size"])}
+  end
+
+  defp get_page(page_string) when is_nil(page_string), do: 1
+
+  defp get_page(page_string) do
+    case Integer.parse(page_string) do
+      {page, _} -> page
+      :error -> 1
+    end
+  end
+
+  defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
+
+  defp get_page_size(page_size_string) do
+    case Integer.parse(page_size_string) do
+      {page_size, _} -> page_size
+      :error -> @users_page_size
+    end
+  end
+end
index 369c111383218974b259b6d9f76b71c9645a362f..80ea283649df068854f0011b54f1c7446899c5a8 100644 (file)
@@ -183,11 +183,11 @@ defmodule Pleroma.Web.Router do
     patch("/users/confirm_email", AdminAPIController, :confirm_email)
     patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
 
-    get("/reports", AdminAPIController, :list_reports)
-    get("/reports/:id", AdminAPIController, :report_show)
-    patch("/reports", AdminAPIController, :reports_update)
-    post("/reports/:id/notes", AdminAPIController, :report_notes_create)
-    delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)
+    get("/reports", ReportController, :index)
+    get("/reports/:id", ReportController, :show)
+    patch("/reports", ReportController, :update)
+    post("/reports/:id/notes", ReportController, :notes_create)
+    delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
 
     get("/statuses/:id", StatusController, :show)
     put("/statuses/:id", StatusController, :update)
index d72851c9e0f1f397fff0fe501fc5577ab34c7784..a1bff5688aeea9a0aec89f84505ee7e6cc46b60d 100644 (file)
@@ -17,7 +17,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
   alias Pleroma.Repo
-  alias Pleroma.ReportNote
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web
@@ -1198,286 +1197,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "GET /api/pleroma/admin/reports/:id" do
-    test "returns report by its id", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports/#{report_id}")
-        |> json_response(:ok)
-
-      assert response["id"] == report_id
-    end
-
-    test "returns 404 when report id is invalid", %{conn: conn} do
-      conn = get(conn, "/api/pleroma/admin/reports/test")
-
-      assert json_response(conn, :not_found) == %{"error" => "Not found"}
-    end
-  end
-
-  describe "PATCH /api/pleroma/admin/reports" do
-    setup do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      {:ok, %{id: second_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel very offended",
-          status_ids: [activity.id]
-        })
-
-      %{
-        id: report_id,
-        second_report_id: second_report_id
-      }
-    end
-
-    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
-      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
-      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
-
-      response =
-        conn
-        |> assign(:token, read_token)
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [%{"state" => "resolved", "id" => id}]
-        })
-        |> json_response(403)
-
-      assert response == %{
-               "error" => "Insufficient permissions: admin:write:reports."
-             }
-
-      conn
-      |> assign(:token, write_token)
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [%{"state" => "resolved", "id" => id}]
-      })
-      |> json_response(:no_content)
-    end
-
-    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "resolved", "id" => id}
-        ]
-      })
-      |> json_response(:no_content)
-
-      activity = Activity.get_by_id(id)
-      assert activity.data["state"] == "resolved"
-
-      log_entry = Repo.one(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
-    end
-
-    test "closes report", %{conn: conn, id: id, admin: admin} do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "closed", "id" => id}
-        ]
-      })
-      |> json_response(:no_content)
-
-      activity = Activity.get_by_id(id)
-      assert activity.data["state"] == "closed"
-
-      log_entry = Repo.one(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'closed' state"
-    end
-
-    test "returns 400 when state is unknown", %{conn: conn, id: id} do
-      conn =
-        conn
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [
-            %{"state" => "test", "id" => id}
-          ]
-        })
-
-      assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
-    end
-
-    test "returns 404 when report is not exist", %{conn: conn} do
-      conn =
-        conn
-        |> patch("/api/pleroma/admin/reports", %{
-          "reports" => [
-            %{"state" => "closed", "id" => "test"}
-          ]
-        })
-
-      assert hd(json_response(conn, :bad_request))["error"] == "not_found"
-    end
-
-    test "updates state of multiple reports", %{
-      conn: conn,
-      id: id,
-      admin: admin,
-      second_report_id: second_report_id
-    } do
-      conn
-      |> patch("/api/pleroma/admin/reports", %{
-        "reports" => [
-          %{"state" => "resolved", "id" => id},
-          %{"state" => "closed", "id" => second_report_id}
-        ]
-      })
-      |> json_response(:no_content)
-
-      activity = Activity.get_by_id(id)
-      second_activity = Activity.get_by_id(second_report_id)
-      assert activity.data["state"] == "resolved"
-      assert second_activity.data["state"] == "closed"
-
-      [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(first_log_entry) ==
-               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
-
-      assert ModerationLog.get_log_entry_message(second_log_entry) ==
-               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
-    end
-  end
-
-  describe "GET /api/pleroma/admin/reports" do
-    test "returns empty response when no reports created", %{conn: conn} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
-
-      assert Enum.empty?(response["reports"])
-      assert response["total"] == 0
-    end
-
-    test "returns reports", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
-
-      [report] = response["reports"]
-
-      assert length(response["reports"]) == 1
-      assert report["id"] == report_id
-
-      assert response["total"] == 1
-    end
-
-    test "returns reports with specified state", %{conn: conn} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: first_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      {:ok, %{id: second_report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I don't like this user"
-        })
-
-      CommonAPI.update_report_state(second_report_id, "closed")
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "open"
-        })
-        |> json_response(:ok)
-
-      [open_report] = response["reports"]
-
-      assert length(response["reports"]) == 1
-      assert open_report["id"] == first_report_id
-
-      assert response["total"] == 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "closed"
-        })
-        |> json_response(:ok)
-
-      [closed_report] = response["reports"]
-
-      assert length(response["reports"]) == 1
-      assert closed_report["id"] == second_report_id
-
-      assert response["total"] == 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "resolved"
-        })
-        |> json_response(:ok)
-
-      assert Enum.empty?(response["reports"])
-      assert response["total"] == 0
-    end
-
-    test "returns 403 when requested by a non-admin" do
-      user = insert(:user)
-      token = insert(:oauth_token, user: user)
-
-      conn =
-        build_conn()
-        |> assign(:user, user)
-        |> assign(:token, token)
-        |> get("/api/pleroma/admin/reports")
-
-      assert json_response(conn, :forbidden) ==
-               %{"error" => "User is not an admin or OAuth admin scope is not granted."}
-    end
-
-    test "returns 403 when requested by anonymous" do
-      conn = get(build_conn(), "/api/pleroma/admin/reports")
-
-      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
-    end
-  end
-
   describe "GET /api/pleroma/admin/config" do
     setup do: clear_config(:configurable_from_database, true)
 
@@ -3195,66 +2914,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "POST /reports/:id/notes" do
-    setup %{conn: conn, admin: admin} do
-      [reporter, target_user] = insert_pair(:user)
-      activity = insert(:note_activity, user: target_user)
-
-      {:ok, %{id: report_id}} =
-        CommonAPI.report(reporter, %{
-          account_id: target_user.id,
-          comment: "I feel offended",
-          status_ids: [activity.id]
-        })
-
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
-        content: "this is disgusting!"
-      })
-
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
-        content: "this is disgusting2!"
-      })
-
-      %{
-        admin_id: admin.id,
-        report_id: report_id
-      }
-    end
-
-    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
-      [note, _] = Repo.all(ReportNote)
-
-      assert %{
-               activity_id: ^report_id,
-               content: "this is disgusting!",
-               user_id: ^admin_id
-             } = note
-    end
-
-    test "it returns reports with notes", %{conn: conn, admin: admin} do
-      conn = get(conn, "/api/pleroma/admin/reports")
-
-      response = json_response(conn, 200)
-      notes = hd(response["reports"])["notes"]
-      [note, _] = notes
-
-      assert note["user"]["nickname"] == admin.nickname
-      assert note["content"] == "this is disgusting!"
-      assert note["created_at"]
-      assert response["total"] == 1
-    end
-
-    test "it deletes the note", %{conn: conn, report_id: report_id} do
-      assert ReportNote |> Repo.all() |> length() == 2
-
-      [note, _] = Repo.all(ReportNote)
-
-      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
-
-      assert ReportNote |> Repo.all() |> length() == 1
-    end
-  end
-
   describe "GET /api/pleroma/admin/config/descriptions" do
     test "structure", %{conn: conn} do
       admin = insert(:user, is_admin: true)
diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/web/admin_api/controllers/report_controller_test.exs
new file mode 100644 (file)
index 0000000..0eddb36
--- /dev/null
@@ -0,0 +1,368 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
+  use Pleroma.Web.ConnCase
+
+  import Pleroma.Factory
+
+  alias Pleroma.Activity
+  alias Pleroma.Config
+  alias Pleroma.ModerationLog
+  alias Pleroma.Repo
+  alias Pleroma.ReportNote
+  alias Pleroma.Web.CommonAPI
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "GET /api/pleroma/admin/reports/:id" do
+    test "returns report by its id", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports/#{report_id}")
+        |> json_response(:ok)
+
+      assert response["id"] == report_id
+    end
+
+    test "returns 404 when report id is invalid", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/reports/test")
+
+      assert json_response(conn, :not_found) == %{"error" => "Not found"}
+    end
+  end
+
+  describe "PATCH /api/pleroma/admin/reports" do
+    setup do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel very offended",
+          status_ids: [activity.id]
+        })
+
+      %{
+        id: report_id,
+        second_report_id: second_report_id
+      }
+    end
+
+    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
+      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
+
+      response =
+        conn
+        |> assign(:token, read_token)
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [%{"state" => "resolved", "id" => id}]
+        })
+        |> json_response(403)
+
+      assert response == %{
+               "error" => "Insufficient permissions: admin:write:reports."
+             }
+
+      conn
+      |> assign(:token, write_token)
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [%{"state" => "resolved", "id" => id}]
+      })
+      |> json_response(:no_content)
+    end
+
+    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
+      conn
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "resolved", "id" => id}
+        ]
+      })
+      |> json_response(:no_content)
+
+      activity = Activity.get_by_id(id)
+      assert activity.data["state"] == "resolved"
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+    end
+
+    test "closes report", %{conn: conn, id: id, admin: admin} do
+      conn
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "closed", "id" => id}
+        ]
+      })
+      |> json_response(:no_content)
+
+      activity = Activity.get_by_id(id)
+      assert activity.data["state"] == "closed"
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'closed' state"
+    end
+
+    test "returns 400 when state is unknown", %{conn: conn, id: id} do
+      conn =
+        conn
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [
+            %{"state" => "test", "id" => id}
+          ]
+        })
+
+      assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
+    end
+
+    test "returns 404 when report is not exist", %{conn: conn} do
+      conn =
+        conn
+        |> patch("/api/pleroma/admin/reports", %{
+          "reports" => [
+            %{"state" => "closed", "id" => "test"}
+          ]
+        })
+
+      assert hd(json_response(conn, :bad_request))["error"] == "not_found"
+    end
+
+    test "updates state of multiple reports", %{
+      conn: conn,
+      id: id,
+      admin: admin,
+      second_report_id: second_report_id
+    } do
+      conn
+      |> patch("/api/pleroma/admin/reports", %{
+        "reports" => [
+          %{"state" => "resolved", "id" => id},
+          %{"state" => "closed", "id" => second_report_id}
+        ]
+      })
+      |> json_response(:no_content)
+
+      activity = Activity.get_by_id(id)
+      second_activity = Activity.get_by_id(second_report_id)
+      assert activity.data["state"] == "resolved"
+      assert second_activity.data["state"] == "closed"
+
+      [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(first_log_entry) ==
+               "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+
+      assert ModerationLog.get_log_entry_message(second_log_entry) ==
+               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/reports" do
+    test "returns empty response when no reports created", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+      assert response["total"] == 0
+    end
+
+    test "returns reports", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports")
+        |> json_response(:ok)
+
+      [report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert report["id"] == report_id
+
+      assert response["total"] == 1
+    end
+
+    test "returns reports with specified state", %{conn: conn} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: first_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      {:ok, %{id: second_report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I don't like this user"
+        })
+
+      CommonAPI.update_report_state(second_report_id, "closed")
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "open"
+        })
+        |> json_response(:ok)
+
+      [open_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert open_report["id"] == first_report_id
+
+      assert response["total"] == 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "closed"
+        })
+        |> json_response(:ok)
+
+      [closed_report] = response["reports"]
+
+      assert length(response["reports"]) == 1
+      assert closed_report["id"] == second_report_id
+
+      assert response["total"] == 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/reports", %{
+          "state" => "resolved"
+        })
+        |> json_response(:ok)
+
+      assert Enum.empty?(response["reports"])
+      assert response["total"] == 0
+    end
+
+    test "returns 403 when requested by a non-admin" do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user)
+
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> get("/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) ==
+               %{"error" => "User is not an admin or OAuth admin scope is not granted."}
+    end
+
+    test "returns 403 when requested by anonymous" do
+      conn = get(build_conn(), "/api/pleroma/admin/reports")
+
+      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+    end
+  end
+
+  describe "POST /api/pleroma/admin/reports/:id/notes" do
+    setup %{conn: conn, admin: admin} do
+      [reporter, target_user] = insert_pair(:user)
+      activity = insert(:note_activity, user: target_user)
+
+      {:ok, %{id: report_id}} =
+        CommonAPI.report(reporter, %{
+          account_id: target_user.id,
+          comment: "I feel offended",
+          status_ids: [activity.id]
+        })
+
+      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+        content: "this is disgusting!"
+      })
+
+      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+        content: "this is disgusting2!"
+      })
+
+      %{
+        admin_id: admin.id,
+        report_id: report_id
+      }
+    end
+
+    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
+      [note, _] = Repo.all(ReportNote)
+
+      assert %{
+               activity_id: ^report_id,
+               content: "this is disgusting!",
+               user_id: ^admin_id
+             } = note
+    end
+
+    test "it returns reports with notes", %{conn: conn, admin: admin} do
+      conn = get(conn, "/api/pleroma/admin/reports")
+
+      response = json_response(conn, 200)
+      notes = hd(response["reports"])["notes"]
+      [note, _] = notes
+
+      assert note["user"]["nickname"] == admin.nickname
+      assert note["content"] == "this is disgusting!"
+      assert note["created_at"]
+      assert response["total"] == 1
+    end
+
+    test "it deletes the note", %{conn: conn, report_id: report_id} do
+      assert ReportNote |> Repo.all() |> length() == 2
+
+      [note, _] = Repo.all(ReportNote)
+
+      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
+
+      assert ReportNote |> Repo.all() |> length() == 1
+    end
+  end
+end