Add report notes
authorMaxim Filippov <colixer@gmail.com>
Tue, 3 Dec 2019 14:54:07 +0000 (23:54 +0900)
committerMaxim Filippov <colixer@gmail.com>
Tue, 3 Dec 2019 15:26:37 +0000 (00:26 +0900)
12 files changed:
CHANGELOG.md
docs/API/admin_api.md
lib/pleroma/activity.ex
lib/pleroma/report_note.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex
lib/pleroma/web/router.ex
priv/repo/migrations/20191203043610_create_report_notes.exs [new file with mode: 0644]
test/web/admin_api/admin_api_controller_test.exs
test/web/admin_api/views/report_view_test.exs

index e44c892abd87d6c83fc06ccf2f2309b1ce564cba..a62222cda00dc58cec1624831ad4b58910c42d26 100644 (file)
@@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - **Breaking:** Admin API: Return link alongside with token on password reset
 - **Breaking:** Admin API: `PUT /api/pleroma/admin/reports/:id` is now `PATCH /api/pleroma/admin/reports`, see admin_api.md for details
 - **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
+- **Breaking** replying to reports is now "report notes", enpoint changed from `POST /api/pleroma/admin/reports/:id/respond` to `POST /api/pleroma/admin/reports/:id/notes`
 - Admin API: Return `total` when querying for reports
 - Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
 - Admin API: Return link alongside with token on password reset
index 2cac317def2df0790ae1ffd04db05ec0fd455c66..e51fad2cb01f41e62320d21ca837fea129284cef 100644 (file)
@@ -607,78 +607,17 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 
   - On success: `204`, empty response
 
-## `POST /api/pleroma/admin/reports/:id/respond`
+## `POST /api/pleroma/admin/reports/:id/notes`
 
-### Respond to a report
+### Create a report note
 
 - Params:
   - `id`
-  - `status`: required, the message
+  - `content`: required, the message
 - Response:
   - On failure:
     - 400 Bad Request `"Invalid parameters"` when `status` is missing
-    - 403 Forbidden `{"error": "error_msg"}`
-    - 404 Not Found `"Not found"`
-  - On success: JSON, created Mastodon Status entity
-
-```json
-{
-  "account": { ... },
-  "application": {
-    "name": "Web",
-    "website": null
-  },
-  "bookmarked": false,
-  "card": null,
-  "content": "Your claim is going to be closed",
-  "created_at": "2019-05-11T17:13:03.000Z",
-  "emojis": [],
-  "favourited": false,
-  "favourites_count": 0,
-  "id": "9ihuiSL1405I65TmEq",
-  "in_reply_to_account_id": null,
-  "in_reply_to_id": null,
-  "language": null,
-  "media_attachments": [],
-  "mentions": [
-    {
-      "acct": "user",
-      "id": "9i6dAJqSGSKMzLG2Lo",
-      "url": "https://pleroma.example.org/users/user",
-      "username": "user"
-    },
-    {
-      "acct": "admin",
-      "id": "9hEkA5JsvAdlSrocam",
-      "url": "https://pleroma.example.org/users/admin",
-      "username": "admin"
-    }
-  ],
-  "muted": false,
-  "pinned": false,
-  "pleroma": {
-    "content": {
-      "text/plain": "Your claim is going to be closed"
-    },
-    "conversation_id": 35,
-    "in_reply_to_account_acct": null,
-    "local": true,
-    "spoiler_text": {
-      "text/plain": ""
-    }
-  },
-  "reblog": null,
-  "reblogged": false,
-  "reblogs_count": 0,
-  "replies_count": 0,
-  "sensitive": false,
-  "spoiler_text": "",
-  "tags": [],
-  "uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
-  "url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
-  "visibility": "direct"
-}
-```
+  - On success: `204`, empty response
 
 ## `PUT /api/pleroma/admin/statuses/:id`
 
index cd7a5aae9aba85848274103c9de1e95f757a78b5..37b2c041eb97222588241f13feabe45ad994d9ae 100644 (file)
@@ -12,6 +12,7 @@ defmodule Pleroma.Activity do
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
+  alias Pleroma.ReportNote
   alias Pleroma.ThreadMute
   alias Pleroma.User
 
@@ -47,6 +48,8 @@ defmodule Pleroma.Activity do
     has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
     # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
     has_one(:bookmark, Bookmark)
+    # This is a fake relation, do not use outside of with_preloaded_report_notes
+    has_many(:report_notes, ReportNote)
     has_many(:notifications, Notification, on_delete: :delete_all)
 
     # Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
@@ -113,6 +116,16 @@ defmodule Pleroma.Activity do
 
   def with_preloaded_bookmark(query, _), do: query
 
+  def with_preloaded_report_notes(query) do
+    from([a] in query,
+      left_join: r in ReportNote,
+      on: a.id == r.activity_id,
+      preload: [report_notes: r]
+    )
+  end
+
+  def with_preloaded_report_notes(query, _), do: query
+
   def with_set_thread_muted_field(query, %User{} = user) do
     from([a] in query,
       left_join: tm in ThreadMute,
diff --git a/lib/pleroma/report_note.ex b/lib/pleroma/report_note.ex
new file mode 100644 (file)
index 0000000..9110269
--- /dev/null
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReportNote do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias Pleroma.Activity
+  alias Pleroma.Repo
+  alias Pleroma.ReportNote
+  alias Pleroma.User
+
+  @type t :: %__MODULE__{}
+
+  schema "report_notes" do
+    field(:content, :string)
+    belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+    belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
+
+    timestamps()
+  end
+
+  @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t(), String.t()) ::
+          {:ok, ReportNote.t()} | {:error, Changeset.t()}
+  def create(user_id, activity_id, content) do
+    attrs = %{
+      user_id: user_id,
+      activity_id: activity_id,
+      content: content
+    }
+
+    %ReportNote{}
+    |> cast(attrs, [:user_id, :activity_id, :content])
+    |> validate_required([:user_id, :activity_id, :content])
+    |> Repo.insert()
+  end
+
+  def get_all_for_status(status_id) do
+    ReportNote
+    |> where(activity_id: ^status_id)
+    |> Repo.all()
+  end
+end
index f32d0417596e1dac0c5a7ae5608fbf96a58d1b13..5c6cdfcf16e5ef40b1a2c06fb2932f7711848b00 100644 (file)
@@ -1018,6 +1018,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> Activity.with_preloaded_bookmark(opts["user"])
   end
 
+  defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do
+    query
+    |> Activity.with_preloaded_report_notes()
+  end
+
+  defp maybe_preload_report_notes(query, _), do: query
+
   defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
 
   defp maybe_set_thread_muted_field(query, opts) do
@@ -1045,6 +1052,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     Activity
     |> maybe_preload_objects(opts)
     |> maybe_preload_bookmarks(opts)
+    |> maybe_preload_report_notes(opts)
     |> maybe_set_thread_muted_field(opts)
     |> maybe_order(opts)
     |> restrict_recipients(recipients, opts["user"])
index 01aacbde3fbac90d68f08d12ed0497383d4ceb22..079d458ba3ae0d02c77bc913c30e4f13f938ca85 100644 (file)
@@ -781,6 +781,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       params
       |> Map.put("type", "Flag")
       |> Map.put("skip_preload", true)
+      |> Map.put("preload_report_notes", true)
       |> Map.put("total", true)
       |> Map.put("limit", page_size)
       |> Map.put("offset", (page - 1) * page_size)
index 24fdc3c821920759735e7372d2bfe8a877f0c7d5..ee32bac45c2e938493d9919b7a59f79aa1eadebc 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Activity
   alias Pleroma.ModerationLog
   alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.ReportNote
   alias Pleroma.User
   alias Pleroma.UserInviteToken
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -641,9 +642,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   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: Utils.get_reports(params, page, page_size)})
+    |> render("index.json", %{reports: reports})
   end
 
   def list_grouped_reports(conn, _params) do
@@ -687,32 +690,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     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")
-
-      {:ok, activity} = CommonAPI.post(user, params)
-
+  def report_notes_create(%{assigns: %{user: user}} = conn, %{
+        "id" => status_id,
+        "content" => content
+      }) do
+    with {:ok, _} <- ReportNote.create(user.id, status_id, content) do
       ModerationLog.insert_log(%{
         action: "report_response",
         actor: user,
-        subject: activity,
-        text: params["status"]
+        subject: Activity.get_by_id(status_id),
+        text: 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
 
index ca88595c722054c4cb1155aca19aea52c5bb1584..80ca6269155f71e842866cabb020815a24486905 100644 (file)
@@ -38,7 +38,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
       content: content,
       created_at: created_at,
       statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
-      state: report.data["state"]
+      state: report.data["state"],
+      notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
     }
   end
 
@@ -62,6 +63,21 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
     }
   end
 
+  def render("index_notes.json", %{notes: notes}) when is_list(notes) do
+    Enum.map(notes, &render(__MODULE__, "show_note.json", &1))
+  end
+
+  def render("index_notes.json", _), do: []
+
+  def render("show_note.json", %{content: content, user_id: user_id}) do
+    user = User.get_by_id(user_id)
+
+    %{
+      content: content,
+      user: merge_account_views(user)
+    }
+  end
+
   defp merge_account_views(%User{} = user) do
     Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user})
     |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}))
index e6c4f6f1495d1c14c6c1eac0218a325e02ab89d5..af220a98b8b072dbf93bc025d00c9ae597558c9a 100644 (file)
@@ -187,7 +187,7 @@ defmodule Pleroma.Web.Router do
     get("/grouped_reports", AdminAPIController, :list_grouped_reports)
     get("/reports/:id", AdminAPIController, :report_show)
     patch("/reports", AdminAPIController, :reports_update)
-    post("/reports/:id/respond", AdminAPIController, :report_respond)
+    post("/reports/:id/notes", AdminAPIController, :report_notes_create)
 
     put("/statuses/:id", AdminAPIController, :status_update)
     delete("/statuses/:id", AdminAPIController, :status_delete)
diff --git a/priv/repo/migrations/20191203043610_create_report_notes.exs b/priv/repo/migrations/20191203043610_create_report_notes.exs
new file mode 100644 (file)
index 0000000..a4f8c09
--- /dev/null
@@ -0,0 +1,13 @@
+defmodule Pleroma.Repo.Migrations.CreateReportNotes do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:report_notes) do
+      add(:user_id, references(:users, type: :uuid))
+      add(:activity_id, references(:activities, type: :uuid))
+      add(:content, :string)
+
+      timestamps()
+    end
+  end
+end
index 32577afeebef7ef1419de2be08d1c03435637180..44557ea451e8b5007d299e4b3774a314eff31640 100644 (file)
@@ -1710,61 +1710,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "POST /api/pleroma/admin/reports/:id/respond" do
-    setup %{conn: conn} do
-      admin = insert(:user, is_admin: true)
-
-      %{conn: assign(conn, :user, admin), admin: admin}
-    end
-
-    test "returns created dm", %{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]
-        })
-
-      response =
-        conn
-        |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
-          "status" => "I will check it out"
-        })
-        |> json_response(:ok)
-
-      recipients = Enum.map(response["mentions"], & &1["username"])
-
-      assert reporter.nickname in recipients
-      assert response["content"] == "I will check it out"
-      assert response["visibility"] == "direct"
-
-      log_entry = Repo.one(ModerationLog)
-
-      assert ModerationLog.get_log_entry_message(log_entry) ==
-               "@#{admin.nickname} responded with 'I will check it out' to report ##{
-                 response["id"]
-               }"
-    end
-
-    test "returns 400 when status is missing", %{conn: conn} do
-      conn = post(conn, "/api/pleroma/admin/reports/test/respond")
-
-      assert json_response(conn, :bad_request) == "Invalid parameters"
-    end
-
-    test "returns 404 when report id is invalid", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/reports/test/respond", %{
-          "status" => "foo"
-        })
-
-      assert json_response(conn, :not_found) == "Not found"
-    end
-  end
-
   describe "PUT /api/pleroma/admin/statuses/:id" do
     setup %{conn: conn} do
       admin = insert(:user, is_admin: true)
@@ -2961,6 +2906,55 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                }"
     end
   end
+
+  describe "POST /reports/:id/notes" do
+    setup do
+      admin = insert(:user, is_admin: true)
+      [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]
+        })
+
+      build_conn()
+      |> assign(:user, admin)
+      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
+        content: "this is disgusting!"
+      })
+
+      %{
+        admin_id: admin.id,
+        report_id: report_id,
+        admin: admin
+      }
+    end
+
+    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
+      assert %{
+               activity_id: ^report_id,
+               content: "this is disgusting!",
+               user_id: ^admin_id
+             } = Repo.one(Pleroma.ReportNote)
+    end
+
+    test "it returns reports with notes", %{admin: admin} do
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> get("/api/pleroma/admin/reports")
+
+      reponse = json_response(conn, 200)
+      notes = hd(reponse["reports"])["notes"]
+      [note] = notes
+
+      assert note["user"]["nickname"] == admin.nickname
+      assert note["content"] == "this is disgusting!"
+    end
+  end
 end
 
 # Needed for testing
index ef4a806e4300fdc8300a82323fedecb9a47a2985..a0c6eab3c546f395af031c611780e5f0ba6f45e0 100644 (file)
@@ -30,6 +30,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
           Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
         ),
       statuses: [],
+      notes: [],
       state: "open",
       id: activity.id
     }
@@ -65,6 +66,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
         ),
       statuses: [StatusView.render("show.json", %{activity: activity})],
       state: "open",
+      notes: [],
       id: report_activity.id
     }