Merge branch 'develop' into openapi/admin/relay

This commit is contained in:
Egor Kislitsyn 2020-06-04 13:30:14 +04:00
commit 70f054b083
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
13 changed files with 806 additions and 462 deletions

View 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)

View file

@ -7,29 +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.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.MastodonAPI
alias Pleroma.Web.Router
require Logger
@ -71,18 +64,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
when action in [:user_follow, :user_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}
@ -277,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
})
conn
|> put_view(MastodonAPI.StatusView)
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity})
else
_ -> {:error, :not_found}
@ -600,85 +581,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)

View file

@ -0,0 +1,107 @@
# 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
plug(Pleroma.Web.ApiSpec.CastAndValidate)
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)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation
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
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}, body_params: %{reports: reports}} = conn, _) do
result =
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)
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}, body_params: %{content: content}} = conn, %{
id: report_id
}) 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
end

View file

@ -41,9 +41,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
def show(conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id(id) do
conn
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("show.json", %{activity: activity})
render(conn, "show.json", %{activity: activity})
else
nil -> {:error, :not_found}
end

View file

@ -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

View file

@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
parameters: [id_param()],
security: [%{"oAuth" => ["read:statuses"]}],
responses: %{
200 => Operation.response("Status", "application/json", Status),
200 => Operation.response("Status", "application/json", status()),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
}
end
defp admin_account do
def admin_account do
%Schema{
type: :object,
properties: %{

View file

@ -113,22 +113,44 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
query
|> prepare_tags()
|> Enum.map(fn tag ->
tag = String.trim_leading(tag, "#")
%{name: tag, url: tags_path <> tag}
end)
end
defp resource_search(:v1, "hashtags", query, _options) do
query
|> prepare_tags()
|> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
prepare_tags(query)
end
defp prepare_tags(query) do
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
defp prepare_tags(query, add_joined_tag \\ true) do
tags =
query
|> String.split(~r/[^#\w]+/u, trim: true)
|> Enum.uniq_by(&String.downcase/1)
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
tags =
if Enum.any?(explicit_tags) do
explicit_tags
else
tags
end
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
if Enum.empty?(explicit_tags) && add_joined_tag do
tags
|> Kernel.++([joined_tag(tags)])
|> Enum.uniq_by(&String.downcase/1)
else
tags
end
end
defp joined_tag(tags) do
tags
|> Enum.map(fn tag -> String.capitalize(tag) end)
|> Enum.join()
end
defp with_fallback(f, fallback \\ []) do

View 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)