- **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
- 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`
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
+ alias Pleroma.ReportNote
alias Pleroma.ThreadMute
alias Pleroma.User
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!
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,
--- /dev/null
+# 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
|> 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
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"])
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)
alias Pleroma.Activity
alias Pleroma.ModerationLog
alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.ReportNote
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
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
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
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
}
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}))
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)
--- /dev/null
+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
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)
}"
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
Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
),
statuses: [],
+ notes: [],
state: "open",
id: activity.id
}
),
statuses: [StatusView.render("show.json", %{activity: activity})],
state: "open",
+ notes: [],
id: report_activity.id
}