Add OpenAPI spec for AdminAPI.ReportController
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 3 Jun 2020 15:10:11 +0000 (19:10 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 3 Jun 2020 15:17:05 +0000 (19:17 +0400)
docs/API/admin_api.md
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/controllers/report_controller.ex
lib/pleroma/web/api_spec/operations/admin/report_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/status_operation.ex
test/web/admin_api/controllers/report_controller_test.exs

index 639c3224ddb7853b7c0d1cbe58e40e7c20bd792b..92816baf9cd6014db6f16ddb37a27410c56b1b42 100644 (file)
@@ -547,7 +547,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 
 ```json
 {
-  "totalReports" : 1,
+  "total" : 1,
   "reports": [
     {
       "account": {
@@ -768,7 +768,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
     - 400 Bad Request `"Invalid parameters"` when `status` is missing
   - On success: `204`, empty response
 
-## `POST /api/pleroma/admin/reports/:report_id/notes/:id`
+## `DELETE /api/pleroma/admin/reports/:report_id/notes/:id`
 
 ### Delete report note
 
index f2375bcc4f7d1119cf923361b58fd920bfdab4f7..a76a699eebcb567364625fd9371b53bd65f3b045 100644 (file)
@@ -740,6 +740,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   def get_reports(params, page, page_size) do
     params =
       params
+      |> Map.new(fn {key, value} -> {to_string(key), value} end)
       |> Map.put("type", "Flag")
       |> Map.put("skip_preload", true)
       |> Map.put("preload_report_notes", true)
index 23f0174d494cd5329f75eeb52489b944b662aaba..4c011e174539b9898249907f8b02948539dcf1e9 100644 (file)
@@ -18,8 +18,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
 
   require Logger
 
-  @users_page_size 50
-
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
   plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show])
 
   plug(
@@ -30,15 +29,15 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
 
   action_fallback(AdminAPI.FallbackController)
 
-  def index(conn, params) do
-    {page, page_size} = page_params(params)
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation
 
-    reports = Utils.get_reports(params, page, page_size)
+  def index(conn, params) do
+    reports = Utils.get_reports(params, params.page, params.page_size)
 
     render(conn, "index.json", reports: reports)
   end
 
-  def show(conn, %{"id" => id}) do
+  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
@@ -46,32 +45,33 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
     end
   end
 
-  def update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
+  def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn, _) 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}
+      Enum.map(reports, fn report ->
+        case CommonAPI.update_report_state(report.id, report.state) do
+          {:ok, activity} ->
+            ModerationLog.insert_log(%{
+              action: "report_update",
+              actor: admin,
+              subject: activity
+            })
+
+            activity
+
+          {: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, "")
+    if Enum.any?(result, &Map.has_key?(&1, :error)) do
+      json_response(conn, :bad_request, result)
+    else
+      json_response(conn, :no_content, "")
     end
   end
 
-  def notes_create(%{assigns: %{user: user}} = conn, %{
-        "id" => report_id,
-        "content" => content
+  def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{
+        id: report_id
       }) do
     with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
       ModerationLog.insert_log(%{
@@ -88,8 +88,8 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
   end
 
   def notes_delete(%{assigns: %{user: user}} = conn, %{
-        "id" => note_id,
-        "report_id" => report_id
+        id: note_id,
+        report_id: report_id
       }) do
     with {:ok, note} <- ReportNote.destroy(note_id) do
       ModerationLog.insert_log(%{
@@ -104,26 +104,4 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
       _ -> 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
diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
new file mode 100644 (file)
index 0000000..15e78bf
--- /dev/null
@@ -0,0 +1,237 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.Status
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Get a list of reports",
+      operationId: "AdminAPI.ReportController.index",
+      security: [%{"oAuth" => ["read:reports"]}],
+      parameters: [
+        Operation.parameter(
+          :state,
+          :query,
+          report_state(),
+          "Filter by report state"
+        ),
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer},
+          "The number of records to retrieve"
+        ),
+        Operation.parameter(
+          :page,
+          :query,
+          %Schema{type: :integer, default: 1},
+          "Page number"
+        ),
+        Operation.parameter(
+          :page_size,
+          :query,
+          %Schema{type: :integer, default: 50},
+          "Number number of log entries per page"
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Response", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              total: %Schema{type: :integer},
+              reports: %Schema{
+                type: :array,
+                items: report()
+              }
+            }
+          }),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Get an individual report",
+      operationId: "AdminAPI.ReportController.show",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["read:reports"]}],
+      responses: %{
+        200 => Operation.response("Report", "application/json", report()),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Change the state of one or multiple reports",
+      operationId: "AdminAPI.ReportController.update",
+      security: [%{"oAuth" => ["write:reports"]}],
+      requestBody: request_body("Parameters", update_request(), required: true),
+      responses: %{
+        204 => no_content_response(),
+        400 => Operation.response("Bad Request", "application/json", update_400_response()),
+        403 => Operation.response("Forbidden", "application/json", ApiError)
+      }
+    }
+  end
+
+  def notes_create_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Create report note",
+      operationId: "AdminAPI.ReportController.notes_create",
+      parameters: [id_param()],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            content: %Schema{type: :string, description: "The message"}
+          }
+        }),
+      security: [%{"oAuth" => ["write:reports"]}],
+      responses: %{
+        204 => no_content_response(),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  def notes_delete_operation do
+    %Operation{
+      tags: ["Admin", "Reports"],
+      summary: "Delete report note",
+      operationId: "AdminAPI.ReportController.notes_delete",
+      parameters: [
+        Operation.parameter(:report_id, :path, :string, "Report ID"),
+        Operation.parameter(:id, :path, :string, "Note ID")
+      ],
+      security: [%{"oAuth" => ["write:reports"]}],
+      responses: %{
+        204 => no_content_response(),
+        404 => Operation.response("Not Found", "application/json", ApiError)
+      }
+    }
+  end
+
+  defp report_state do
+    %Schema{type: :string, enum: ["open", "closed", "resolved"]}
+  end
+
+  defp id_param do
+    Operation.parameter(:id, :path, FlakeID, "Report ID",
+      example: "9umDrYheeY451cQnEe",
+      required: true
+    )
+  end
+
+  defp report do
+    %Schema{
+      type: :object,
+      properties: %{
+        id: FlakeID,
+        state: report_state(),
+        account: account_admin(),
+        actor: account_admin(),
+        content: %Schema{type: :string},
+        created_at: %Schema{type: :string, format: :"date-time"},
+        statuses: %Schema{type: :array, items: Status},
+        notes: %Schema{
+          type: :array,
+          items: %Schema{
+            type: :object,
+            properties: %{
+              id: %Schema{type: :integer},
+              user_id: FlakeID,
+              content: %Schema{type: :string},
+              inserted_at: %Schema{type: :string, format: :"date-time"}
+            }
+          }
+        }
+      }
+    }
+  end
+
+  defp account_admin do
+    %Schema{
+      title: "Account",
+      description: "Account view for admins",
+      type: :object,
+      properties:
+        Map.merge(Account.schema().properties, %{
+          nickname: %Schema{type: :string},
+          deactivated: %Schema{type: :boolean},
+          local: %Schema{type: :boolean},
+          roles: %Schema{
+            type: :object,
+            properties: %{
+              admin: %Schema{type: :boolean},
+              moderator: %Schema{type: :boolean}
+            }
+          },
+          confirmation_pending: %Schema{type: :boolean}
+        })
+    }
+  end
+
+  defp update_request do
+    %Schema{
+      type: :object,
+      required: [:reports],
+      properties: %{
+        reports: %Schema{
+          type: :array,
+          items: %Schema{
+            type: :object,
+            properties: %{
+              id: %Schema{allOf: [FlakeID], description: "Required, report ID"},
+              state: %Schema{
+                type: :string,
+                description:
+                  "Required, the new state. Valid values are `open`, `closed` and `resolved`"
+              }
+            }
+          },
+          example: %{
+            "reports" => [
+              %{"id" => "123", "state" => "closed"},
+              %{"id" => "1337", "state" => "resolved"}
+            ]
+          }
+        }
+      }
+    }
+  end
+
+  defp update_400_response do
+    %Schema{
+      type: :array,
+      items: %Schema{
+        type: :object,
+        properties: %{
+          id: %Schema{allOf: [FlakeID], description: "Report ID"},
+          error: %Schema{type: :string, description: "Error message"}
+        }
+      }
+    }
+  end
+end
index 2947e6b34926cd2150b0fc5c1dd43343e4f346a3..745399b4b08bcda7472e9589f28e46b3c5038472 100644 (file)
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
     }
   end
 
-  defp admin_account do
+  def admin_account do
     %Schema{
       type: :object,
       properties: %{
index 0eddb369cc49f750f912cb85e1c8477ad42d6a81..940bce340d2f26082ecb343e24e0fb70f76063f6 100644 (file)
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
       response =
         conn
         |> get("/api/pleroma/admin/reports/#{report_id}")
-        |> json_response(:ok)
+        |> json_response_and_validate_schema(:ok)
 
       assert response["id"] == report_id
     end
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
     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"}
+      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"}
     end
   end
 
@@ -85,10 +85,11 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
       response =
         conn
         |> assign(:token, read_token)
+        |> put_req_header("content-type", "application/json")
         |> patch("/api/pleroma/admin/reports", %{
           "reports" => [%{"state" => "resolved", "id" => id}]
         })
-        |> json_response(403)
+        |> json_response_and_validate_schema(403)
 
       assert response == %{
                "error" => "Insufficient permissions: admin:write:reports."
@@ -96,20 +97,22 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
 
       conn
       |> assign(:token, write_token)
+      |> put_req_header("content-type", "application/json")
       |> patch("/api/pleroma/admin/reports", %{
         "reports" => [%{"state" => "resolved", "id" => id}]
       })
-      |> json_response(:no_content)
+      |> json_response_and_validate_schema(:no_content)
     end
 
     test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
       conn
+      |> put_req_header("content-type", "application/json")
       |> patch("/api/pleroma/admin/reports", %{
         "reports" => [
           %{"state" => "resolved", "id" => id}
         ]
       })
-      |> json_response(:no_content)
+      |> json_response_and_validate_schema(:no_content)
 
       activity = Activity.get_by_id(id)
       assert activity.data["state"] == "resolved"
@@ -122,12 +125,13 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
 
     test "closes report", %{conn: conn, id: id, admin: admin} do
       conn
+      |> put_req_header("content-type", "application/json")
       |> patch("/api/pleroma/admin/reports", %{
         "reports" => [
           %{"state" => "closed", "id" => id}
         ]
       })
-      |> json_response(:no_content)
+      |> json_response_and_validate_schema(:no_content)
 
       activity = Activity.get_by_id(id)
       assert activity.data["state"] == "closed"
@@ -141,25 +145,28 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
     test "returns 400 when state is unknown", %{conn: conn, id: id} do
       conn =
         conn
+        |> put_req_header("content-type", "application/json")
         |> patch("/api/pleroma/admin/reports", %{
           "reports" => [
             %{"state" => "test", "id" => id}
           ]
         })
 
-      assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
+      assert "Unsupported state" =
+               hd(json_response_and_validate_schema(conn, :bad_request))["error"]
     end
 
     test "returns 404 when report is not exist", %{conn: conn} do
       conn =
         conn
+        |> put_req_header("content-type", "application/json")
         |> patch("/api/pleroma/admin/reports", %{
           "reports" => [
             %{"state" => "closed", "id" => "test"}
           ]
         })
 
-      assert hd(json_response(conn, :bad_request))["error"] == "not_found"
+      assert hd(json_response_and_validate_schema(conn, :bad_request))["error"] == "not_found"
     end
 
     test "updates state of multiple reports", %{
@@ -169,13 +176,14 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
       second_report_id: second_report_id
     } do
       conn
+      |> put_req_header("content-type", "application/json")
       |> patch("/api/pleroma/admin/reports", %{
         "reports" => [
           %{"state" => "resolved", "id" => id},
           %{"state" => "closed", "id" => second_report_id}
         ]
       })
-      |> json_response(:no_content)
+      |> json_response_and_validate_schema(:no_content)
 
       activity = Activity.get_by_id(id)
       second_activity = Activity.get_by_id(second_report_id)
@@ -197,7 +205,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
       response =
         conn
         |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
+        |> json_response_and_validate_schema(:ok)
 
       assert Enum.empty?(response["reports"])
       assert response["total"] == 0
@@ -217,7 +225,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
       response =
         conn
         |> get("/api/pleroma/admin/reports")
-        |> json_response(:ok)
+        |> json_response_and_validate_schema(:ok)
 
       [report] = response["reports"]
 
@@ -248,12 +256,10 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
 
       response =
         conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "open"
-        })
-        |> json_response(:ok)
+        |> get("/api/pleroma/admin/reports?state=open")
+        |> json_response_and_validate_schema(:ok)
 
-      [open_report] = response["reports"]
+      assert [open_report] = response["reports"]
 
       assert length(response["reports"]) == 1
       assert open_report["id"] == first_report_id
@@ -262,27 +268,22 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
 
       response =
         conn
-        |> get("/api/pleroma/admin/reports", %{
-          "state" => "closed"
-        })
-        |> json_response(:ok)
+        |> get("/api/pleroma/admin/reports?state=closed")
+        |> json_response_and_validate_schema(:ok)
 
-      [closed_report] = response["reports"]
+      assert [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
+      assert %{"total" => 0, "reports" => []} ==
+               conn
+               |> get("/api/pleroma/admin/reports?state=resolved", %{
+                 "" => ""
+               })
+               |> json_response_and_validate_schema(:ok)
     end
 
     test "returns 403 when requested by a non-admin" do
@@ -302,7 +303,9 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
     test "returns 403 when requested by anonymous" do
       conn = get(build_conn(), "/api/pleroma/admin/reports")
 
-      assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+      assert json_response(conn, :forbidden) == %{
+               "error" => "Invalid credentials."
+             }
     end
   end
 
@@ -318,11 +321,15 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
           status_ids: [activity.id]
         })
 
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
         content: "this is disgusting!"
       })
 
-      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
         content: "this is disgusting2!"
       })
 
@@ -333,7 +340,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
     end
 
     test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
-      [note, _] = Repo.all(ReportNote)
+      assert [note, _] = Repo.all(ReportNote)
 
       assert %{
                activity_id: ^report_id,
@@ -345,7 +352,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
     test "it returns reports with notes", %{conn: conn, admin: admin} do
       conn = get(conn, "/api/pleroma/admin/reports")
 
-      response = json_response(conn, 200)
+      response = json_response_and_validate_schema(conn, 200)
       notes = hd(response["reports"])["notes"]
       [note, _] = notes
 
@@ -357,8 +364,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
 
     test "it deletes the note", %{conn: conn, report_id: report_id} do
       assert ReportNote |> Repo.all() |> length() == 2
-
-      [note, _] = Repo.all(ReportNote)
+      assert [note, _] = Repo.all(ReportNote)
 
       delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")