Ability to control the output of account/pleroma/relationship in statuses in order to improve the rendering performance.

See `[:extensions, output_relationships_in_statuses_by_default]` setting and `with_relationships` param.
This commit is contained in:
Ivan Tashkinov 2020-04-01 19:49:09 +03:00
parent bfec45bf74
commit 2f2bd7fe72
25 changed files with 278 additions and 76 deletions

View file

@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
- Configuration: `:extensions/:output_relationships_in_statuses_by_default` option (if `false`, disables the output of account/pleroma/relationship for statuses and notifications by default, breaking the compatibility with older PleromaFE versions).
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
- Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
@ -20,7 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [2.0.0] - 2019-03-08 ## [2.0.0] - 2019-03-08
### Security ### Security
- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. - Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
### Removed ### Removed
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`

View file

@ -386,47 +386,56 @@ defmodule Pleroma.LoadTesting.Fetcher do
favourites = ActivityPub.fetch_favourites(user) favourites = ActivityPub.fetch_favourites(user)
output_relationships =
!!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
Benchee.run( Benchee.run(
%{ %{
"Rendering home timeline" => fn -> "Rendering home timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: home_activities, activities: home_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering direct timeline" => fn -> "Rendering direct timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: direct_activities, activities: direct_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering public timeline" => fn -> "Rendering public timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: public_activities, activities: public_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering tag timeline" => fn -> "Rendering tag timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: tag_activities, activities: tag_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering notifications" => fn -> "Rendering notifications" => fn ->
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
notifications: notifications, notifications: notifications,
for: user for: user,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering favourites timeline" => fn -> "Rendering favourites timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: favourites, activities: favourites,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end end
}, },

View file

@ -262,6 +262,8 @@ config :pleroma, :instance,
extended_nickname_format: true, extended_nickname_format: true,
cleanup_attachments: false cleanup_attachments: false
config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
config :pleroma, :feed, config :pleroma, :feed,
post_title: %{ post_title: %{
max_length: 100, max_length: 100,

View file

@ -121,6 +121,22 @@ config :pleroma, :config_description, [
} }
] ]
}, },
%{
group: :pleroma,
key: :extensions,
type: :group,
description: "Pleroma-specific extensions",
children: [
%{
key: :output_relationships_in_statuses_by_default,
type: :beeolean,
description:
"If `true`, outputs account/pleroma/relationship map for each rendered status / notification (for all clients). " <>
"If `false`, outputs the above only if `with_relationships` param is tru-ish " <>
"(that breaks compatibility with older PleromaFE versions which do not send this param but expect the output)."
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: Pleroma.Uploaders.Local, key: Pleroma.Uploaders.Local,

View file

@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities, activities: activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: true
}) })
end end
}, },

View file

@ -129,17 +129,27 @@ defmodule Pleroma.UserRelationship do
end end
@doc ":relationships option for StatusView / AccountView / NotificationView" @doc ":relationships option for StatusView / AccountView / NotificationView"
def view_relationships_option(nil = _reading_user, _actors) do def view_relationships_option(reading_user, actors, opts \\ [])
def view_relationships_option(nil = _reading_user, _actors, _opts) do
%{user_relationships: [], following_relationships: []} %{user_relationships: [], following_relationships: []}
end end
def view_relationships_option(%User{} = reading_user, actors) do def view_relationships_option(%User{} = reading_user, actors, opts) do
{source_to_target_rel_types, target_to_source_rel_types} =
if opts[:source_mutes_only] do
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
{[:mute], []}
else
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
end
user_relationships = user_relationships =
UserRelationship.dictionary( UserRelationship.dictionary(
[reading_user], [reading_user],
actors, actors,
[:block, :mute, :notification_mute, :reblog_mute], source_to_target_rel_types,
[:block, :inverse_subscription] target_to_source_rel_types
) )
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors) following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)

View file

@ -258,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn conn
|> put_view(Pleroma.Web.AdminAPI.StatusView) |> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do def list_user_statuses(conn, %{"nickname" => nickname} = params) do
@ -277,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
else else
_ -> {:error, :not_found} _ -> {:error, :not_found}
end end
@ -801,7 +801,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn conn
|> put_view(Pleroma.Web.AdminAPI.StatusView) |> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do

View file

@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
actor: merge_account_views(user), actor: merge_account_views(user),
content: content, content: content,
created_at: created_at, created_at: created_at,
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), statuses:
StatusView.render("index.json", %{
activities: statuses,
as: :activity,
skip_relationships: false
}),
state: report.data["state"], state: report.data["state"],
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
} }

View file

@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end end
defp preview?(draft) do defp preview?(draft) do
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
%__MODULE__{draft | preview?: preview?} %__MODULE__{draft | preview?: preview?}
end end

View file

@ -5,10 +5,18 @@
defmodule Pleroma.Web.ControllerHelper do defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller use Pleroma.Web, :controller
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html alias Pleroma.Config
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
def truthy_param?(value), do: value not in @falsy_param_values def explicitly_falsy_param?(value), do: value in @falsy_param_values
# Note: `nil` and `""` are considered falsy values in Pleroma
def falsy_param?(value),
do: explicitly_falsy_param?(value) or value in [nil, ""]
def truthy_param?(value), do: not falsy_param?(value)
def json_response(conn, status, json) do def json_response(conn, status, json) do
conn conn
@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do
def put_if_exist(map, _key, nil), do: map def put_if_exist(map, _key, nil), do: map
def put_if_exist(map, key, value), do: Map.put(map, key, value) def put_if_exist(map, key, value), do: Map.put(map, key, value)
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
def skip_relationships?(params) do
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
false
else
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
not truthy_param?(params["with_relationships"])
end
end
end end

View file

@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] only: [
add_link_headers: 2,
truthy_param?: 1,
assign_account_by_id: 2,
json_response: 3,
skip_relationships?: 1
]
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
@ -237,7 +243,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", activities: activities, for: reading_user, as: :activity) |> render("index.json",
activities: activities,
for: reading_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_e -> render_error(conn, :not_found, "Can't find user") _e -> render_error(conn, :not_found, "Can't find user")
end end

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.NotificationController do defmodule Pleroma.Web.MastodonAPI.NotificationController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
conn conn
|> add_link_headers(notifications) |> add_link_headers(notifications)
|> render("index.json", notifications: notifications, for: user) |> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/notifications/:id # GET /api/v1/notifications/:id

View file

@ -5,13 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp search_options(params, user) do defp search_options(params, user) do
[ [
skip_relationships: skip_relationships?(params),
resolve: params["resolve"] == "true", resolve: params["resolve"] == "true",
following: params["following"] == "true", following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"), limit: fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"), offset: fetch_integer_param(params, "offset"),
type: params["type"], type: params["type"],
author: get_author(params), author: get_author(params),
for_user: user for_user: user
@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp resource_search(_, "accounts", query, options) do defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end) accounts = with_fallback(fn -> User.search(query, options) end)
AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
as: :user,
skip_relationships: false
)
end end
defp resource_search(_, "statuses", query, options) do defp resource_search(_, "statuses", query, options) do
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
StatusView.render("index.json",
activities: statuses,
for: options[:for_user],
as: :activity,
skip_relationships: options[:skip_relationships]
)
end end
defp resource_search(:v2, "hashtags", query, _options) do defp resource_search(:v2, "hashtags", query, _options) do

View file

@ -5,7 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2] import Pleroma.Web.ControllerHelper,
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
require Ecto.Query require Ecto.Query
@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
`ids` query param is required `ids` query param is required
""" """
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
limit = 100 limit = 100
activities = activities =
@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|> Activity.all_by_ids_with_object() |> Activity.all_by_ids_with_object()
|> Enum.filter(&Visibility.visible_for_user?(&1, user)) |> Enum.filter(&Visibility.visible_for_user?(&1, user))
render(conn, "index.json", activities: activities, for: user, as: :activity) render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc """ @doc """
@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc "GET /api/v1/bookmarks" @doc "GET /api/v1/bookmarks"
@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn conn
|> add_link_headers(bookmarks) |> add_link_headers(bookmarks)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
# TODO: Replace with a macro when there is a Phoenix release with # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
# in it
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/direct # GET /api/v1/timelines/direct
@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/public # GET /api/v1/timelines/public
@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn conn
|> add_link_headers(activities, %{"local" => local_only}) |> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
render_error(conn, :unauthorized, "authorization required for timeline view") render_error(conn, :unauthorized, "authorization required for timeline view")
end end
@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn conn
|> add_link_headers(activities, %{"local" => local_only}) |> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/list/:list_id # GET /api/v1/timelines/list/:list_id
@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> ActivityPub.fetch_activities_bounded(following, params) |> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse() |> Enum.reverse()
render(conn, "index.json", activities: activities, for: user, as: :activity) render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_e -> render_error(conn, :forbidden, "Error.") _e -> render_error(conn, :forbidden, "Error.")
end end

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
def render("index.json", %{users: users} = opts) do def render("index.json", %{users: users} = opts) do
# Note: :skip_relationships option is currently intentionally not supported for accounts
relationships_opt = relationships_opt =
cond do cond do
Map.has_key?(opts, :relationships) -> Map.has_key?(opts, :relationships) ->
@ -190,11 +191,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end) end)
relationship = relationship =
render("relationship.json", %{ if opts[:skip_relationships] do
user: opts[:for], %{}
target: user, else
relationships: opts[:relationships] render("relationship.json", %{
}) user: opts[:for],
target: user,
relationships: opts[:relationships]
})
end
%{ %{
id: to_string(user.id), id: to_string(user.id),

View file

@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Kernel.++(move_activities_targets) |> Kernel.++(move_activities_targets)
UserRelationship.view_relationships_option(reading_user, actors) UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
)
end end
opts = %{ opts =
for: reading_user, opts
parent_activities: parent_activities, |> Map.put(:parent_activities, parent_activities)
relationships: relationships_opt |> Map.put(:relationships, relationships_opt)
}
safe_render_many(notifications, NotificationView, "show.json", opts) safe_render_many(notifications, NotificationView, "show.json", opts)
end end
@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
mastodon_type = Activity.mastodon_notification_type(activity) mastodon_type = Activity.mastodon_notification_type(activity)
render_opts = %{
relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}
with %{id: _} = account <- with %{id: _} = account <-
AccountView.render("show.json", %{ AccountView.render(
user: actor, "show.json",
for: reading_user, Map.merge(render_opts, %{user: actor, for: reading_user})
relationships: opts[:relationships] ) do
}) do
response = %{ response = %{
id: to_string(notification.id), id: to_string(notification.id),
type: mastodon_type, type: mastodon_type,
@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
} }
} }
render_opts = %{relationships: opts[:relationships]}
case mastodon_type do case mastodon_type do
"mention" -> "mention" ->
put_status(response, activity, reading_user, render_opts) put_status(response, activity, reading_user, render_opts)
@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
put_status(response, parent_activity_fn.(), reading_user, render_opts) put_status(response, parent_activity_fn.(), reading_user, render_opts)
"move" -> "move" ->
# Note: :skip_relationships option being applied to _account_ rendering (here)
put_target(response, activity, reading_user, render_opts) put_target(response, activity, reading_user, render_opts)
"follow" -> "follow" ->

View file

@ -97,7 +97,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
true -> true ->
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"])) actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
UserRelationship.view_relationships_option(opts[:for], actors) UserRelationship.view_relationships_option(opts[:for], actors,
source_mutes_only: opts[:skip_relationships]
)
end end
opts = opts =
@ -151,7 +153,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
relationships: opts[:relationships] relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}), }),
in_reply_to_id: nil, in_reply_to_id: nil,
in_reply_to_account_id: nil, in_reply_to_account_id: nil,
@ -299,6 +302,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
_ -> [] _ -> []
end end
# Status muted state (would do 1 request per status unless user mutes are preloaded)
muted = muted =
thread_muted? || thread_muted? ||
UserRelationship.exists?( UserRelationship.exists?(
@ -317,7 +321,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
relationships: opts[:relationships] relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}), }),
in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -139,7 +139,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", activities: activities, for: for_user, as: :activity) |> render("index.json",
activities: activities,
for: for_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc "POST /api/v1/pleroma/accounts/:id/subscribe" @doc "POST /api/v1/pleroma/accounts/:id/subscribe"

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
@ -130,7 +130,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_error -> _error ->
conn conn
@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
end end
end end
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
with notifications <- Notification.set_read_up_to(user, max_id) do with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80) notifications = Enum.take(notifications, 80)
conn conn
|> put_view(NotificationView) |> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user}) |> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end end
end end
end end

View file

@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
import Ecto.Query import Ecto.Query
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.User
alias Pleroma.Repo alias Pleroma.Repo
def up do def up do

View file

@ -1,6 +1,5 @@
defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
use Ecto.Migration use Ecto.Migration
alias Pleroma.User
def change do def change do
execute(""" execute("""

View file

@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
import Pleroma.Factory import Pleroma.Factory
test "does NOT render account/pleroma/relationship if this is disabled by default" do
clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
response =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
|> json_response(200)
assert Enum.all?(response, fn n ->
get_in(n, ["account", "pleroma", "relationship"]) == %{}
end)
end
test "list of notifications" do test "list of notifications" do
%{user: user, conn: conn} = oauth_access(["read:notifications"]) %{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user) other_user = insert(:user)

View file

@ -1043,6 +1043,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
end end
test "bookmarks" do test "bookmarks" do
bookmarks_uri = "/api/v1/bookmarks?with_relationships=true"
%{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
author = insert(:user) author = insert(:user)
@ -1064,7 +1066,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
assert json_response(response2, 200)["bookmarked"] == true assert json_response(response2, 200)["bookmarked"] == true
bookmarks = get(conn, "/api/v1/bookmarks") bookmarks = get(conn, bookmarks_uri)
assert [json_response(response2, 200), json_response(response1, 200)] == assert [json_response(response2, 200), json_response(response1, 200)] ==
json_response(bookmarks, 200) json_response(bookmarks, 200)
@ -1073,7 +1075,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
assert json_response(response1, 200)["bookmarked"] == false assert json_response(response1, 200)["bookmarked"] == false
bookmarks = get(conn, "/api/v1/bookmarks") bookmarks = get(conn, bookmarks_uri)
assert [json_response(response2, 200)] == json_response(bookmarks, 200) assert [json_response(response2, 200)] == json_response(bookmarks, 200)
end end

View file

@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
describe "home" do describe "home" do
setup do: oauth_access(["read:statuses"]) setup do: oauth_access(["read:statuses"])
test "does NOT render account/pleroma/relationship if this is disabled by default", %{
user: user,
conn: conn
} do
clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
response =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/home")
|> json_response(200)
assert Enum.all?(response, fn n ->
get_in(n, ["account", "pleroma", "relationship"]) == %{}
end)
end
test "the home timeline", %{user: user, conn: conn} do test "the home timeline", %{user: user, conn: conn} do
uri = "/api/v1/timelines/home?with_relationships=true"
following = insert(:user, nickname: "followed") following = insert(:user, nickname: "followed")
third_user = insert(:user, nickname: "repeated") third_user = insert(:user, nickname: "repeated")
@ -28,13 +51,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
{:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"}) {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
{:ok, _, _} = CommonAPI.repeat(activity.id, following) {:ok, _, _} = CommonAPI.repeat(activity.id, following)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert Enum.empty?(json_response(ret_conn, :ok)) assert Enum.empty?(json_response(ret_conn, :ok))
{:ok, _user} = User.follow(user, following) {:ok, _user} = User.follow(user, following)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert [ assert [
%{ %{
@ -59,7 +82,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
{:ok, _user} = User.follow(third_user, user) {:ok, _user} = User.follow(third_user, user)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert [ assert [
%{ %{