Add Reports to Admin API
This commit is contained in:
parent
e190b3022b
commit
e2b3a27204
16 changed files with 945 additions and 20 deletions
|
@ -24,6 +24,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Admin API: Endpoints for listing/revoking invite tokens
|
||||
- Admin API: Endpoints for making users follow/unfollow each other
|
||||
- Admin API: added filters (role, tags, email, name) for users endpoint
|
||||
- Admin API: Endpoints for managing reports
|
||||
- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
|
||||
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
|
||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||
|
|
|
@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
|
|||
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"page_size": integer,
|
||||
"count": integer,
|
||||
|
@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
|
|||
- `nickname`
|
||||
- Response: User’s object
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"deactivated": bool,
|
||||
"id": integer,
|
||||
|
@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
|
@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"is_moderator": bool,
|
||||
"is_admin": bool
|
||||
|
@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Params: none
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
|
||||
"invites": [
|
||||
|
@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- `token`
|
||||
- Response:
|
||||
|
||||
```JSON
|
||||
```json
|
||||
{
|
||||
"id": integer,
|
||||
"token": string,
|
||||
|
@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- Methods: `GET`
|
||||
- Params: none
|
||||
- Response: password reset token (base64 string)
|
||||
|
||||
## `/api/pleroma/admin/reports`
|
||||
### Get a list of reports
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
|
||||
- `limit`: optional, the number of records to retrieve
|
||||
- `since_id`: optional, returns results that are more recent than the specified id
|
||||
- `max_id`: optional, returns results that are older than the specified id
|
||||
- Response:
|
||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||
- On success: JSON, returns a list of reports, where:
|
||||
- `account`: the user who has been reported
|
||||
- `actor`: the user who has sent the report
|
||||
- `statuses`: list of statuses that have been included to the report
|
||||
|
||||
```json
|
||||
{
|
||||
"reports": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "user",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-04-23T17:32:04.000Z",
|
||||
"display_name": "User",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 3,
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
},
|
||||
"actor": {
|
||||
"acct": "lain",
|
||||
"avatar": "https://pleroma.example.org/images/avi.png",
|
||||
"avatar_static": "https://pleroma.example.org/images/avi.png",
|
||||
"bot": false,
|
||||
"created_at": "2019-03-28T17:36:03.000Z",
|
||||
"display_name": "Roger Braun",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"header": "https://pleroma.example.org/images/banner.png",
|
||||
"header_static": "https://pleroma.example.org/images/banner.png",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"locked": false,
|
||||
"note": "",
|
||||
"pleroma": {
|
||||
"confirmation_pending": false,
|
||||
"hide_favorites": false,
|
||||
"hide_followers": false,
|
||||
"hide_follows": false,
|
||||
"is_admin": false,
|
||||
"is_moderator": false,
|
||||
"relationship": {},
|
||||
"tags": []
|
||||
},
|
||||
"source": {
|
||||
"note": "",
|
||||
"pleroma": {},
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 1,
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
"content": "Please delete it",
|
||||
"created_at": "2019-04-29T19:48:15.000Z",
|
||||
"id": "9iJGOv1j8hxuw19bcm",
|
||||
"state": "open",
|
||||
"statuses": [
|
||||
{
|
||||
"account": { ... },
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
|
||||
"created_at": "2019-04-23T19:15:47.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9i6mQ9uVrrOmOime8m",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [
|
||||
{
|
||||
"acct": "lain",
|
||||
"id": "9hEkA5JsvAdlSrocam",
|
||||
"url": "https://pleroma.example.org/users/lain",
|
||||
"username": "lain"
|
||||
},
|
||||
{
|
||||
"acct": "user",
|
||||
"id": "9i6dAJqSGSKMzLG2Lo",
|
||||
"url": "https://pleroma.example.org/users/user",
|
||||
"username": "user"
|
||||
}
|
||||
],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "@lain click on my link https://www.google.com/"
|
||||
},
|
||||
"conversation_id": 28,
|
||||
"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/8717b90f-8e09-4b58-97b0-e3305472b396",
|
||||
"url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
|
||||
"visibility": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Get an individual report
|
||||
- Method `GET`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id`
|
||||
### Change the state of the report
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported state"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Report object (see above)
|
||||
|
||||
## `/api/pleroma/admin/reports/:id/respond`
|
||||
### Respond to a report
|
||||
- Method `POST`
|
||||
- Params:
|
||||
- `id`
|
||||
- `status`: 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"
|
||||
}
|
||||
```
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Change the scope of an individual reported status
|
||||
- Method `PUT`
|
||||
- Params:
|
||||
- `id`
|
||||
- `sensitive`: optional, valid values are `true` or `false`
|
||||
- `visibility`: optional, valid values are `public`, `private` and `unlisted`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"Unsupported visibility"`
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: JSON, Mastodon Status entity
|
||||
|
||||
## `/api/pleroma/admin/statuses/:id`
|
||||
### Delete an individual reported status
|
||||
- Method `DELETE`
|
||||
- Params:
|
||||
- `id`
|
||||
- Response:
|
||||
- On failure:
|
||||
- 403 Forbidden `{"error": "error_msg"}`
|
||||
- 404 Not Found `"Not found"`
|
||||
- On success: 200 OK `{}`
|
||||
|
|
|
@ -111,7 +111,7 @@ def get_bookmark(_, _), do: nil
|
|||
|
||||
def change(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:data])
|
||||
|> cast(params, [:data, :recipients])
|
||||
|> validate_required([:data])
|
||||
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
|
||||
end
|
||||
|
|
|
@ -29,7 +29,7 @@ def report(to, reporter, account, statuses, comment) do
|
|||
end
|
||||
|
||||
statuses_html =
|
||||
if length(statuses) > 0 do
|
||||
if is_list(statuses) && length(statuses) > 0 do
|
||||
statuses_list_html =
|
||||
statuses
|
||||
|> Enum.map(fn
|
||||
|
|
|
@ -703,6 +703,12 @@ defp restrict_type(query, %{"type" => type}) do
|
|||
|
||||
defp restrict_type(query, _), do: query
|
||||
|
||||
defp restrict_state(query, %{"state" => state}) do
|
||||
from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
|
||||
end
|
||||
|
||||
defp restrict_state(query, _), do: query
|
||||
|
||||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||
from(
|
||||
activity in query,
|
||||
|
@ -855,6 +861,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_local(opts)
|
||||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_muted(opts)
|
||||
|
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
require Logger
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page"]
|
||||
@supported_report_states ~w(open closed resolved)
|
||||
@valid_visibilities ~w(public unlisted private direct)
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
|
@ -670,7 +672,8 @@ def make_flag_data(params, additional) do
|
|||
"actor" => params.actor.ap_id,
|
||||
"content" => params.content,
|
||||
"object" => object,
|
||||
"context" => params.context
|
||||
"context" => params.context,
|
||||
"state" => "open"
|
||||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
@ -713,4 +716,77 @@ def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
#### Report-related helpers
|
||||
|
||||
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
|
||||
with new_data <- Map.put(activity.data, "state", state),
|
||||
changeset <- Changeset.change(activity, data: new_data),
|
||||
{:ok, activity} <- Repo.update(changeset) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||
|
||||
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
|
||||
[to, cc, recipients] =
|
||||
activity
|
||||
|> get_updated_targets(visibility)
|
||||
|> Enum.map(&Enum.uniq/1)
|
||||
|
||||
object_data =
|
||||
activity.object.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
{:ok, object} =
|
||||
activity.object
|
||||
|> Object.change(%{data: object_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
activity_data =
|
||||
activity.data
|
||||
|> Map.put("to", to)
|
||||
|> Map.put("cc", cc)
|
||||
|
||||
activity
|
||||
|> Map.put(:object, object)
|
||||
|> Activity.change(%{data: activity_data, recipients: recipients})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
|
||||
|
||||
defp get_updated_targets(
|
||||
%Activity{data: %{"to" => to} = data, recipients: recipients},
|
||||
visibility
|
||||
) do
|
||||
cc = Map.get(data, "cc", [])
|
||||
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
to = [public | List.delete(to, follower_address)]
|
||||
cc = [follower_address | List.delete(cc, public)]
|
||||
recipients = [public | recipients]
|
||||
[to, cc, recipients]
|
||||
|
||||
"private" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = List.delete(cc, public)
|
||||
recipients = List.delete(recipients, public)
|
||||
[to, cc, recipients]
|
||||
|
||||
"unlisted" ->
|
||||
to = [follower_address | List.delete(to, public)]
|
||||
cc = [public | List.delete(cc, follower_address)]
|
||||
recipients = recipients ++ [follower_address, public]
|
||||
[to, cc, recipients]
|
||||
|
||||
_ ->
|
||||
[to, cc, recipients]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
|
@ -287,12 +292,88 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|
|||
|> json(token.token)
|
||||
end
|
||||
|
||||
def list_reports(conn, params) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Flag")
|
||||
|> Map.put("skip_preload", true)
|
||||
|
||||
reports =
|
||||
[]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
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: report})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def report_update_state(conn, %{"id" => id, "state" => state}) do
|
||||
with {:ok, report} <- CommonAPI.update_report_state(id, state) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", %{report: report})
|
||||
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)
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
else
|
||||
true ->
|
||||
{:param_cast, nil}
|
||||
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def status_update(conn, %{"id" => id} = params) do
|
||||
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("status.json", %{activity: activity})
|
||||
end
|
||||
end
|
||||
|
||||
def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:error, reason}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json(reason)
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|
|
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
41
lib/pleroma/web/admin_api/views/report_view.ex
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", %{reports: reports}) do
|
||||
%{
|
||||
reports: render_many(reports, __MODULE__, "show.json", as: :report)
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{report: report}) do
|
||||
user = User.get_cached_by_ap_id(report.data["actor"])
|
||||
created_at = Utils.to_masto_date(report.data["published"])
|
||||
|
||||
[account_ap_id | status_ap_ids] = report.data["object"]
|
||||
account = User.get_cached_by_ap_id(account_ap_id)
|
||||
|
||||
statuses =
|
||||
Enum.map(status_ap_ids, fn ap_id ->
|
||||
Activity.get_by_ap_id_with_object(ap_id)
|
||||
end)
|
||||
|
||||
%{
|
||||
id: report.id,
|
||||
account: AccountView.render("account.json", %{user: account}),
|
||||
actor: AccountView.render("account.json", %{user: user}),
|
||||
content: report.data["content"],
|
||||
created_at: created_at,
|
||||
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
|
||||
state: report.data["state"]
|
||||
}
|
||||
end
|
||||
end
|
|
@ -71,6 +71,9 @@ def delete(activity_id, user) do
|
|||
{:ok, _} <- unpin(activity_id, user),
|
||||
{:ok, delete} <- ActivityPub.delete(object) do
|
||||
{:ok, delete}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Could not delete"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -315,6 +318,60 @@ def report(user, data) do
|
|||
end
|
||||
end
|
||||
|
||||
def update_report_state(activity_id, state) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(activity_id),
|
||||
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
|
||||
_ ->
|
||||
{:error, "Could not update state"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
{:ok, activity} <- toggle_sensitive(activity, opts),
|
||||
{:ok, activity} <- set_visibility(activity, opts) do
|
||||
{:ok, activity}
|
||||
else
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
|
||||
toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
|
||||
end
|
||||
|
||||
defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
|
||||
when is_boolean(sensitive) do
|
||||
new_data = Map.put(object.data, "sensitive", sensitive)
|
||||
|
||||
{:ok, object} =
|
||||
object
|
||||
|> Object.change(%{data: new_data})
|
||||
|> Object.update_and_set_cache()
|
||||
|
||||
{:ok, Map.put(activity, :object, object)}
|
||||
end
|
||||
|
||||
defp toggle_sensitive(activity, _), do: {:ok, activity}
|
||||
|
||||
defp set_visibility(activity, %{"visibility" => visibility}) do
|
||||
Utils.update_activity_visibility(activity, visibility)
|
||||
end
|
||||
|
||||
defp set_visibility(activity, _), do: {:ok, activity}
|
||||
|
||||
def hide_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
|
|
|
@ -237,13 +237,11 @@ def make_note_data(
|
|||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||
}
|
||||
|
||||
if in_reply_to do
|
||||
in_reply_to_object = Object.normalize(in_reply_to)
|
||||
|
||||
object
|
||||
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
||||
with false <- is_nil(in_reply_to),
|
||||
%Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
|
||||
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
||||
else
|
||||
object
|
||||
_ -> object
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
|
||||
get("/reports", AdminAPIController, :list_reports)
|
||||
get("/reports/:id", AdminAPIController, :report_show)
|
||||
put("/reports/:id", AdminAPIController, :report_update_state)
|
||||
post("/reports/:id/respond", AdminAPIController, :report_respond)
|
||||
|
||||
put("/statuses/:id", AdminAPIController, :status_update)
|
||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Pleroma.Repo.Migrations.SetDefaultStateToReports do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute """
|
||||
UPDATE activities AS a
|
||||
SET data = jsonb_set(data, '{state}', '"open"', true)
|
||||
WHERE data->>'type' = 'Flag'
|
||||
"""
|
||||
end
|
||||
|
||||
def down do
|
||||
execute """
|
||||
UPDATE activities AS a
|
||||
SET data = data #- '{state}'
|
||||
WHERE data->>'type' = 'Flag'
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -43,7 +43,7 @@ def user_factory do
|
|||
def note_factory(attrs \\ %{}) do
|
||||
text = sequence(:text, &"This is :moominmamma: note #{&1}")
|
||||
|
||||
user = insert(:user)
|
||||
user = attrs[:user] || insert(:user)
|
||||
|
||||
data = %{
|
||||
"type" => "Note",
|
||||
|
@ -113,7 +113,8 @@ def direct_note_activity_factory do
|
|||
end
|
||||
|
||||
def note_activity_factory(attrs \\ %{}) do
|
||||
note = attrs[:note] || insert(:note)
|
||||
user = attrs[:user] || insert(:user)
|
||||
note = attrs[:note] || insert(:note, user: user)
|
||||
|
||||
data = %{
|
||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.CommonAPI
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "/api/pleroma/admin/users" do
|
||||
|
@ -949,4 +951,329 @@ test "with token" do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/reports/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
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) == "Not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /api/pleroma/admin/reports/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{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]
|
||||
})
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: report_id}
|
||||
end
|
||||
|
||||
test "mark report as resolved", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["state"] == "resolved"
|
||||
end
|
||||
|
||||
test "closes report", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["state"] == "closed"
|
||||
end
|
||||
|
||||
test "returns 400 when state is unknown", %{conn: conn, id: id} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
|
||||
|
||||
assert json_response(conn, :bad_request) == "Unsupported state"
|
||||
end
|
||||
|
||||
test "returns 404 when report is not exist", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
|
||||
|
||||
assert json_response(conn, :not_found) == "Not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/reports" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
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"])
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/reports", %{
|
||||
"state" => "resolved"
|
||||
})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert Enum.empty?(response["reports"])
|
||||
end
|
||||
|
||||
test "returns 403 when requested by a non-admin" do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|
||||
assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
|
||||
end
|
||||
|
||||
test "returns 403 when requested by anonymous" do
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/pleroma/admin/reports")
|
||||
|
||||
assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/reports/:id/respond" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
|
||||
%{conn: assign(conn, :user, admin)}
|
||||
end
|
||||
|
||||
test "returns created dm", %{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
|
||||
|> 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 conn.assigns[:user].nickname in recipients
|
||||
assert reporter.nickname in recipients
|
||||
assert response["content"] == "I will check it out"
|
||||
assert response["visibility"] == "direct"
|
||||
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, info: %{is_admin: true})
|
||||
activity = insert(:note_activity)
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: activity.id}
|
||||
end
|
||||
|
||||
test "toggle sensitive flag", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["sensitive"]
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
|
||||
|> json_response(:ok)
|
||||
|
||||
refute response["sensitive"]
|
||||
end
|
||||
|
||||
test "change visibility flag", %{conn: conn, id: id} do
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "public"
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "private"
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
|
||||
|> json_response(:ok)
|
||||
|
||||
assert response["visibility"] == "unlisted"
|
||||
end
|
||||
|
||||
test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
|
||||
conn =
|
||||
conn
|
||||
|> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
|
||||
|
||||
assert json_response(conn, :bad_request) == "Unsupported visibility"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /api/pleroma/admin/statuses/:id" do
|
||||
setup %{conn: conn} do
|
||||
admin = insert(:user, info: %{is_admin: true})
|
||||
activity = insert(:note_activity)
|
||||
|
||||
%{conn: assign(conn, :user, admin), id: activity.id}
|
||||
end
|
||||
|
||||
test "deletes status", %{conn: conn, id: id} do
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/statuses/#{id}")
|
||||
|> json_response(:ok)
|
||||
|
||||
refute Activity.get_by_id(id)
|
||||
end
|
||||
|
||||
test "returns error when status is not exist", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/statuses/test")
|
||||
|
||||
assert json_response(conn, :bad_request) == "Could not delete"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -261,10 +261,41 @@ test "creates a report" do
|
|||
data: %{
|
||||
"type" => "Flag",
|
||||
"content" => ^comment,
|
||||
"object" => [^target_ap_id, ^activity_ap_id]
|
||||
"object" => [^target_ap_id, ^activity_ap_id],
|
||||
"state" => "open"
|
||||
}
|
||||
} = flag_activity
|
||||
end
|
||||
|
||||
test "updates report state" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
{:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
|
||||
|
||||
assert report.data["state"] == "resolved"
|
||||
end
|
||||
|
||||
test "does not update report state when state is unsupported" do
|
||||
[reporter, target_user] = insert_pair(:user)
|
||||
activity = insert(:note_activity, user: target_user)
|
||||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
})
|
||||
|
||||
assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
|
||||
end
|
||||
end
|
||||
|
||||
describe "reblog muting" do
|
||||
|
|
|
@ -2129,7 +2129,7 @@ test "returns favorited DM only when user is logged in and he is one of recipien
|
|||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert length(anonymous_response) == 0
|
||||
assert Enum.empty?(anonymous_response)
|
||||
end
|
||||
|
||||
test "does not return others' favorited DM when user is not one of recipients", %{
|
||||
|
@ -2153,7 +2153,7 @@ test "does not return others' favorited DM when user is not one of recipients",
|
|||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert length(response) == 0
|
||||
assert Enum.empty?(response)
|
||||
end
|
||||
|
||||
test "paginates favorites using since_id and max_id", %{
|
||||
|
|
Loading…
Reference in a new issue