mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2026-02-15 17:16:57 +00:00
Merge branch 'replies_collection' into 'develop'
Provide full replies collection in ActivityPub objects (ported from akkoma) See merge request pleroma/pleroma!4370
This commit is contained in:
commit
1a313fa30c
17 changed files with 508 additions and 158 deletions
1
changelog.d/replies-collection.add
Normal file
1
changelog.d/replies-collection.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Provide full replies collection in ActivityPub objects
|
||||||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do
|
||||||
Contains queries for Activity.
|
Contains queries for Activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, only: [from: 2, where: 3]
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
@type query :: Ecto.Queryable.t() | Pleroma.Activity.t()
|
@type query :: Ecto.Queryable.t() | Pleroma.Activity.t()
|
||||||
|
|
||||||
|
|
@ -70,22 +70,6 @@ defmodule Pleroma.Activity.Queries do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query
|
|
||||||
def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do
|
|
||||||
query =
|
|
||||||
if opts[:skip_preloading] do
|
|
||||||
Activity.with_joined_object(query)
|
|
||||||
else
|
|
||||||
Activity.with_preloaded_object(query)
|
|
||||||
end
|
|
||||||
|
|
||||||
where(
|
|
||||||
query,
|
|
||||||
[activity, object: o],
|
|
||||||
fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec by_type(query, String.t()) :: query
|
@spec by_type(query, String.t()) :: query
|
||||||
def by_type(query \\ Activity, activity_type) do
|
def by_type(query \\ Activity, activity_type) do
|
||||||
from(
|
from(
|
||||||
|
|
|
||||||
|
|
@ -398,28 +398,6 @@ defmodule Pleroma.Object do
|
||||||
String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")
|
String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")
|
||||||
end
|
end
|
||||||
|
|
||||||
def replies(object, opts \\ []) do
|
|
||||||
object = Object.normalize(object, fetch: false)
|
|
||||||
|
|
||||||
query =
|
|
||||||
Object
|
|
||||||
|> where(
|
|
||||||
[o],
|
|
||||||
fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"])
|
|
||||||
)
|
|
||||||
|> order_by([o], asc: o.id)
|
|
||||||
|
|
||||||
if opts[:self_only] do
|
|
||||||
actor = object.data["actor"]
|
|
||||||
where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor))
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self_replies(object, opts \\ []),
|
|
||||||
do: replies(object, Keyword.put(opts, :self_only, true))
|
|
||||||
|
|
||||||
def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
|
def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
|
||||||
|
|
||||||
def tags(_), do: []
|
def tags(_), do: []
|
||||||
|
|
|
||||||
|
|
@ -95,13 +95,30 @@ defmodule Pleroma.Pagination do
|
||||||
offset: :integer,
|
offset: :integer,
|
||||||
limit: :integer,
|
limit: :integer,
|
||||||
skip_extra_order: :boolean,
|
skip_extra_order: :boolean,
|
||||||
skip_order: :boolean
|
skip_order: :boolean,
|
||||||
|
order_asc: :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
changeset.changes
|
changeset.changes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp order_statement(query, table_binding, :asc) do
|
||||||
|
order_by(
|
||||||
|
query,
|
||||||
|
[{u, table_position(query, table_binding)}],
|
||||||
|
fragment("? asc nulls last", u.id)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp order_statement(query, table_binding, :desc) do
|
||||||
|
order_by(
|
||||||
|
query,
|
||||||
|
[{u, table_position(query, table_binding)}],
|
||||||
|
fragment("? desc nulls last", u.id)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do
|
defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do
|
||||||
where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id)
|
where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id)
|
||||||
end
|
end
|
||||||
|
|
@ -119,19 +136,16 @@ defmodule Pleroma.Pagination do
|
||||||
defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
|
defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
|
||||||
|
|
||||||
defp restrict(query, :order, %{min_id: _}, table_binding) do
|
defp restrict(query, :order, %{min_id: _}, table_binding) do
|
||||||
order_by(
|
order_statement(query, table_binding, :asc)
|
||||||
query,
|
|
||||||
[{u, table_position(query, table_binding)}],
|
|
||||||
fragment("? asc nulls last", u.id)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :order, _options, table_binding) do
|
defp restrict(query, :order, %{max_id: _}, table_binding) do
|
||||||
order_by(
|
order_statement(query, table_binding, :desc)
|
||||||
query,
|
end
|
||||||
[{u, table_position(query, table_binding)}],
|
|
||||||
fragment("? desc nulls last", u.id)
|
defp restrict(query, :order, options, table_binding) do
|
||||||
)
|
dir = if options[:order_asc], do: :asc, else: :desc
|
||||||
|
order_statement(query, table_binding, dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :offset, %{offset: offset}, _table_binding) do
|
defp restrict(query, :offset, %{offset: offset}, _table_binding) do
|
||||||
|
|
@ -151,11 +165,9 @@ defmodule Pleroma.Pagination do
|
||||||
|
|
||||||
defp restrict(query, _, _, _), do: query
|
defp restrict(query, _, _, _), do: query
|
||||||
|
|
||||||
defp enforce_order(result, %{min_id: _}) do
|
defp enforce_order(result, %{min_id: _, order_asc: true}), do: result
|
||||||
result
|
defp enforce_order(result, %{min_id: _}), do: Enum.reverse(result)
|
||||||
|> Enum.reverse()
|
defp enforce_order(result, %{max_id: _, order_asc: true}), do: Enum.reverse(result)
|
||||||
end
|
|
||||||
|
|
||||||
defp enforce_order(result, _), do: result
|
defp enforce_order(result, _), do: result
|
||||||
|
|
||||||
defp table_position(%Ecto.Query{} = query, binding_name) do
|
defp table_position(%Ecto.Query{} = query, binding_name) do
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_objects_for_replies_collection(parent_ap_id, opts \\ %{}) do
|
||||||
|
opts =
|
||||||
|
opts
|
||||||
|
|> Map.put(:order_asc, true)
|
||||||
|
|> Map.put(:id_type, :integer)
|
||||||
|
|
||||||
|
from(o in Object,
|
||||||
|
where:
|
||||||
|
fragment("?->>'inReplyTo' = ?", o.data, ^parent_ap_id) and
|
||||||
|
fragment(
|
||||||
|
"(?->'to' \\? ?::text OR ?->'cc' \\? ?::text)",
|
||||||
|
o.data,
|
||||||
|
^Pleroma.Constants.as_public(),
|
||||||
|
o.data,
|
||||||
|
^Pleroma.Constants.as_public()
|
||||||
|
) and
|
||||||
|
fragment("?->>'type' <> 'Answer'", o.data),
|
||||||
|
select: %{id: o.id, ap_id: fragment("?->>'id'", o.data)}
|
||||||
|
)
|
||||||
|
|> Pagination.fetch_paginated(opts, :keyset)
|
||||||
|
end
|
||||||
|
|
||||||
@spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
|
@spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
|
||||||
Ecto.UUID.t() | nil
|
Ecto.UUID.t() | nil
|
||||||
def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
|
def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|
|
||||||
@federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
|
@federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
|
||||||
|
|
||||||
|
@object_replies_known_param_keys ["page", "min_id", "max_id", "since_id", "limit"]
|
||||||
|
|
||||||
plug(FederatingPlug when action in @federating_only_actions)
|
plug(FederatingPlug when action in @federating_only_actions)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
|
@ -95,6 +97,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_replies(%{assigns: assigns, query_params: params} = conn, _all_params) do
|
||||||
|
object_ap_id = conn.path_info |> Enum.reverse() |> tl() |> Enum.reverse()
|
||||||
|
object_ap_id = Endpoint.url() <> "/" <> Enum.join(object_ap_id, "/")
|
||||||
|
|
||||||
|
# Most other API params are converted to atoms by OpenAPISpex 3.x
|
||||||
|
# and therefore helper functions assume atoms. For consistency,
|
||||||
|
# also convert our params to atoms here.
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.take(@object_replies_known_param_keys)
|
||||||
|
|> Enum.into(%{}, fn {k, v} -> {String.to_existing_atom(k), v} end)
|
||||||
|
|> Map.put(:object_ap_id, object_ap_id)
|
||||||
|
|> Map.put(:order_asc, true)
|
||||||
|
|> Map.put(:conn, conn)
|
||||||
|
|
||||||
|
with %Object{} = object <- Object.get_cached_by_ap_id(object_ap_id),
|
||||||
|
user <- Map.get(assigns, :user, nil),
|
||||||
|
{_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
|
||||||
|
conn
|
||||||
|
|> maybe_skip_cache(user)
|
||||||
|
|> set_cache_ttl_for(object)
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(ObjectView)
|
||||||
|
|> render("object_replies.json", render_params: params)
|
||||||
|
else
|
||||||
|
{:visible?, false} -> {:error, :not_found}
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def track_object_fetch(conn, nil), do: conn
|
def track_object_fetch(conn, nil), do: conn
|
||||||
|
|
||||||
def track_object_fetch(conn, object_id) do
|
def track_object_fetch(conn, object_id) do
|
||||||
|
|
@ -257,8 +289,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|> render("activity_collection_page.json", %{
|
|> render("activity_collection_page.json", %{
|
||||||
activities: activities,
|
activities: activities,
|
||||||
pagination: ControllerHelper.get_pagination_fields(conn, activities),
|
pagination: ControllerHelper.get_pagination_fields(conn, activities)
|
||||||
iri: "#{user.ap_id}/outbox"
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -404,8 +435,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|> render("activity_collection_page.json", %{
|
|> render("activity_collection_page.json", %{
|
||||||
activities: activities,
|
activities: activities,
|
||||||
pagination: ControllerHelper.get_pagination_fields(conn, activities),
|
pagination: ControllerHelper.get_pagination_fields(conn, activities)
|
||||||
iri: "#{user.ap_id}/inbox"
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,20 +56,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
|
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
|
||||||
defp fix_tag(data), do: Map.drop(data, ["tag"])
|
defp fix_tag(data), do: Map.drop(data, ["tag"])
|
||||||
|
|
||||||
|
# legacy internal *oma format
|
||||||
|
defp fix_replies(%{"replies" => replies} = data) when is_list(replies), do: data
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
|
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
|
||||||
when is_list(replies),
|
when is_list(replies),
|
||||||
do: Map.put(data, "replies", replies)
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
|
defp fix_replies(%{"replies" => %{"first" => %{"orderedItems" => replies}}} = data)
|
||||||
|
when is_list(replies),
|
||||||
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
|
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
|
||||||
do: Map.put(data, "replies", replies)
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
# TODO: Pleroma does not have any support for Collections at the moment.
|
defp fix_replies(%{"replies" => %{"orderedItems" => replies}} = data) when is_list(replies),
|
||||||
# If the `replies` field is not something the ObjectID validator can handle,
|
do: Map.put(data, "replies", replies)
|
||||||
# the activity/object would be rejected, which is bad behavior.
|
|
||||||
defp fix_replies(%{"replies" => replies} = data) when not is_list(replies),
|
|
||||||
do: Map.drop(data, ["replies"])
|
|
||||||
|
|
||||||
defp fix_replies(data), do: data
|
defp fix_replies(data), do: Map.delete(data, "replies")
|
||||||
|
|
||||||
def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
|
def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
|
||||||
do: Map.put(data, "attachment", [attachment])
|
do: Map.put(data, "attachment", [attachment])
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
import Ecto.Query
|
|
||||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
@ -762,48 +761,26 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
def set_quote_url(obj), do: obj
|
def set_quote_url(obj), do: obj
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
|
Inline first page of the `replies` collection,
|
||||||
Based on Mastodon's ActivityPub::NoteSerializer#replies.
|
containing any replies in chronological order.
|
||||||
"""
|
"""
|
||||||
def set_replies(obj_data) do
|
def set_replies(%{"type" => type} = obj_data)
|
||||||
replies_uris =
|
when type in Pleroma.Constants.status_object_types() do
|
||||||
with limit when limit > 0 <-
|
with obj_ap_id when is_binary(obj_ap_id) <- obj_data["id"],
|
||||||
Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0),
|
limit when limit > 0 <-
|
||||||
%Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do
|
Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0),
|
||||||
object
|
collection <-
|
||||||
|> Object.self_replies()
|
Pleroma.Web.ActivityPub.ObjectView.render("object_replies.json", %{
|
||||||
|> select([o], fragment("?->>'id'", o.data))
|
render_params: %{object_ap_id: obj_data["id"], limit: limit, skip_ap_ctx: true}
|
||||||
|> limit(^limit)
|
}) do
|
||||||
|> Repo.all()
|
Map.put(obj_data, "replies", collection)
|
||||||
else
|
else
|
||||||
_ -> []
|
0 -> Map.put(obj_data, "replies", obj_data["id"] <> "/replies")
|
||||||
end
|
_ -> obj_data
|
||||||
|
end
|
||||||
set_replies(obj_data, replies_uris)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_replies(obj, []) do
|
def set_replies(obj_data), do: obj_data
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_replies(obj, replies_uris) do
|
|
||||||
replies_collection = %{
|
|
||||||
"type" => "Collection",
|
|
||||||
"items" => replies_uris
|
|
||||||
}
|
|
||||||
|
|
||||||
Map.merge(obj, %{"replies" => replies_collection})
|
|
||||||
end
|
|
||||||
|
|
||||||
def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do
|
|
||||||
items
|
|
||||||
end
|
|
||||||
|
|
||||||
def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do
|
|
||||||
items
|
|
||||||
end
|
|
||||||
|
|
||||||
def replies(_), do: []
|
|
||||||
|
|
||||||
# Prepares the object of an outgoing create activity.
|
# Prepares the object of an outgoing create activity.
|
||||||
def prepare_object(object) do
|
def prepare_object(object) do
|
||||||
|
|
|
||||||
59
lib/pleroma/web/activity_pub/views/collection_view_helper.ex
Normal file
59
lib/pleroma/web/activity_pub/views/collection_view_helper.ex
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# Copyright © 2025 Akkoma Authors <https://akkoma.dev/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.CollectionViewHelper do
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
def collection_page_offset(collection, iri, page, show_items \\ true, total \\ nil) do
|
||||||
|
offset = (page - 1) * 10
|
||||||
|
items = Enum.slice(collection, offset, 10)
|
||||||
|
items = Enum.map(items, fn user -> user.ap_id end)
|
||||||
|
total = total || length(collection)
|
||||||
|
|
||||||
|
map = %{
|
||||||
|
"id" => "#{iri}?page=#{page}",
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"partOf" => iri,
|
||||||
|
"totalItems" => total,
|
||||||
|
"orderedItems" => if(show_items, do: items, else: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset + 10 < total do
|
||||||
|
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
||||||
|
else
|
||||||
|
map
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_omit_next(pagination, _items, nil), do: pagination
|
||||||
|
|
||||||
|
defp maybe_omit_next(pagination, items, limit) when is_binary(limit) do
|
||||||
|
case Integer.parse(limit) do
|
||||||
|
{limit, ""} -> maybe_omit_next(pagination, items, limit)
|
||||||
|
_ -> maybe_omit_next(pagination, items, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_omit_next(pagination, items, limit) when is_number(limit) do
|
||||||
|
if Enum.count(items) < limit, do: Map.delete(pagination, "next"), else: pagination
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection_page_keyset(
|
||||||
|
display_items,
|
||||||
|
pagination,
|
||||||
|
limit \\ nil,
|
||||||
|
skip_ap_context \\ false
|
||||||
|
) do
|
||||||
|
%{
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"orderedItems" => display_items
|
||||||
|
}
|
||||||
|
|> Map.merge(pagination)
|
||||||
|
|> maybe_omit_next(display_items, limit)
|
||||||
|
|> then(fn m ->
|
||||||
|
if skip_ap_context, do: m, else: Map.merge(m, Utils.make_json_ld_header())
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -6,7 +6,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.CollectionViewHelper
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ControllerHelper
|
||||||
|
|
||||||
def render("object.json", %{object: %Object{} = object}) do
|
def render("object.json", %{object: %Object{} = object}) do
|
||||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(object.data)
|
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(object.data)
|
||||||
|
|
@ -19,4 +22,90 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
||||||
{:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, ap_data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
ap_data
|
ap_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("object_replies.json", %{
|
||||||
|
conn: conn,
|
||||||
|
render_params: %{object_ap_id: object_ap_id, page: "true"} = params
|
||||||
|
}) do
|
||||||
|
params = Map.put_new(params, :limit, 40)
|
||||||
|
|
||||||
|
items = ActivityPub.fetch_objects_for_replies_collection(object_ap_id, params)
|
||||||
|
display_items = map_reply_collection_items(items)
|
||||||
|
|
||||||
|
pagination = ControllerHelper.get_pagination_fields(conn, items, %{}, :asc)
|
||||||
|
|
||||||
|
CollectionViewHelper.collection_page_keyset(display_items, pagination, params[:limit])
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(
|
||||||
|
"object_replies.json",
|
||||||
|
%{
|
||||||
|
render_params: %{object_ap_id: object_ap_id} = params
|
||||||
|
} = opts
|
||||||
|
) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.drop([:max_id, :min_id, :since_id, :object_ap_id])
|
||||||
|
|> Map.put_new(:limit, 40)
|
||||||
|
|> Map.put(:total, true)
|
||||||
|
|
||||||
|
%{total: total, items: items} =
|
||||||
|
ActivityPub.fetch_objects_for_replies_collection(object_ap_id, params)
|
||||||
|
|
||||||
|
display_items = map_reply_collection_items(items)
|
||||||
|
|
||||||
|
first_pagination = reply_collection_first_pagination(items, opts)
|
||||||
|
|
||||||
|
col_ap =
|
||||||
|
%{
|
||||||
|
"id" => object_ap_id <> "/replies",
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => total
|
||||||
|
}
|
||||||
|
|
||||||
|
col_ap =
|
||||||
|
if total > 0 do
|
||||||
|
first_page =
|
||||||
|
CollectionViewHelper.collection_page_keyset(
|
||||||
|
display_items,
|
||||||
|
first_pagination,
|
||||||
|
params[:limit],
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
Map.put(col_ap, "first", first_page)
|
||||||
|
else
|
||||||
|
col_ap
|
||||||
|
end
|
||||||
|
|
||||||
|
if params[:skip_ap_ctx] do
|
||||||
|
col_ap
|
||||||
|
else
|
||||||
|
Map.merge(col_ap, Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp map_reply_collection_items(items), do: Enum.map(items, fn %{ap_id: ap_id} -> ap_id end)
|
||||||
|
|
||||||
|
defp reply_collection_first_pagination(items, %{conn: %Plug.Conn{} = conn}) do
|
||||||
|
pagination = ControllerHelper.get_pagination_fields(conn, items, %{"page" => true}, :asc)
|
||||||
|
Map.put(pagination, "id", Phoenix.Controller.current_url(conn, %{"page" => true}))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reply_collection_first_pagination(items, %{render_params: %{object_ap_id: object_ap_id}}) do
|
||||||
|
%{
|
||||||
|
"id" => object_ap_id <> "/replies?page=true",
|
||||||
|
"partOf" => object_ap_id <> "/replies"
|
||||||
|
}
|
||||||
|
|> then(fn m ->
|
||||||
|
case items do
|
||||||
|
[] ->
|
||||||
|
m
|
||||||
|
|
||||||
|
i ->
|
||||||
|
next_id = object_ap_id <> "/replies?page=true&min_id=#{List.last(i)[:id]}"
|
||||||
|
Map.put(m, "next", next_id)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.CollectionViewHelper
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
@ -164,7 +165,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(following, "#{user.ap_id}/following", page, showing_items, total)
|
CollectionViewHelper.collection_page_offset(
|
||||||
|
following,
|
||||||
|
"#{user.ap_id}/following",
|
||||||
|
page,
|
||||||
|
showing_items,
|
||||||
|
total
|
||||||
|
)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -189,7 +196,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" =>
|
"first" =>
|
||||||
if showing_items do
|
if showing_items do
|
||||||
collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
|
CollectionViewHelper.collection_page_offset(
|
||||||
|
following,
|
||||||
|
"#{user.ap_id}/following",
|
||||||
|
1,
|
||||||
|
!user.hide_follows
|
||||||
|
)
|
||||||
else
|
else
|
||||||
"#{user.ap_id}/following?page=1"
|
"#{user.ap_id}/following?page=1"
|
||||||
end
|
end
|
||||||
|
|
@ -212,7 +224,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
|
CollectionViewHelper.collection_page_offset(
|
||||||
|
followers,
|
||||||
|
"#{user.ap_id}/followers",
|
||||||
|
page,
|
||||||
|
showing_items,
|
||||||
|
total
|
||||||
|
)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -236,7 +254,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"first" =>
|
"first" =>
|
||||||
if showing_items do
|
if showing_items do
|
||||||
collection(followers, "#{user.ap_id}/followers", 1, showing_items, total)
|
CollectionViewHelper.collection_page_offset(
|
||||||
|
followers,
|
||||||
|
"#{user.ap_id}/followers",
|
||||||
|
1,
|
||||||
|
showing_items
|
||||||
|
)
|
||||||
else
|
else
|
||||||
"#{user.ap_id}/followers?page=1"
|
"#{user.ap_id}/followers?page=1"
|
||||||
end
|
end
|
||||||
|
|
@ -256,7 +279,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
|
|
||||||
def render("activity_collection_page.json", %{
|
def render("activity_collection_page.json", %{
|
||||||
activities: activities,
|
activities: activities,
|
||||||
iri: iri,
|
|
||||||
pagination: pagination
|
pagination: pagination
|
||||||
}) do
|
}) do
|
||||||
collection =
|
collection =
|
||||||
|
|
@ -265,13 +287,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
data
|
data
|
||||||
end)
|
end)
|
||||||
|
|
||||||
%{
|
CollectionViewHelper.collection_page_keyset(collection, pagination)
|
||||||
"type" => "OrderedCollectionPage",
|
|
||||||
"partOf" => iri,
|
|
||||||
"orderedItems" => collection
|
|
||||||
}
|
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|
||||||
|> Map.merge(pagination)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("featured.json", %{
|
def render("featured.json", %{
|
||||||
|
|
@ -299,27 +315,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
Map.put(map, "totalItems", total)
|
Map.put(map, "totalItems", total)
|
||||||
end
|
end
|
||||||
|
|
||||||
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
|
|
||||||
offset = (page - 1) * 10
|
|
||||||
items = Enum.slice(collection, offset, 10)
|
|
||||||
items = Enum.map(items, fn user -> user.ap_id end)
|
|
||||||
total = total || length(collection)
|
|
||||||
|
|
||||||
map = %{
|
|
||||||
"id" => "#{iri}?page=#{page}",
|
|
||||||
"type" => "OrderedCollectionPage",
|
|
||||||
"partOf" => iri,
|
|
||||||
"totalItems" => total,
|
|
||||||
"orderedItems" => if(show_items, do: items, else: [])
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset < total do
|
|
||||||
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
|
||||||
else
|
|
||||||
map
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_make_image(func, description, key, user) do
|
defp maybe_make_image(func, description, key, user) do
|
||||||
if image = func.(user, no_default: true) do
|
if image = func.(user, no_default: true) do
|
||||||
%{
|
%{
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.Web.ControllerHelper do
|
||||||
|
|
||||||
# TODO: Only fetch the params from open_api_spex when everything is converted
|
# TODO: Only fetch the params from open_api_spex when everything is converted
|
||||||
@id_keys Pagination.page_keys() -- ["limit", "order"]
|
@id_keys Pagination.page_keys() -- ["limit", "order"]
|
||||||
defp build_pagination_fields(conn, min_id, max_id, extra_params) do
|
defp build_pagination_fields(conn, min_id, max_id, extra_params, order) do
|
||||||
params =
|
params =
|
||||||
if Map.has_key?(conn.private, :open_api_spex) do
|
if Map.has_key?(conn.private, :open_api_spex) do
|
||||||
get_in(conn, [Access.key(:private), Access.key(:open_api_spex), Access.key(:params)])
|
get_in(conn, [Access.key(:private), Access.key(:open_api_spex), Access.key(:params)])
|
||||||
|
|
@ -66,27 +66,50 @@ defmodule Pleroma.Web.ControllerHelper do
|
||||||
|> Map.merge(extra_params)
|
|> Map.merge(extra_params)
|
||||||
|> Map.drop(@id_keys)
|
|> Map.drop(@id_keys)
|
||||||
|
|
||||||
|
{{next_id, nid}, {prev_id, pid}} =
|
||||||
|
if order == :desc,
|
||||||
|
do: {{:max_id, max_id}, {:min_id, min_id}},
|
||||||
|
else: {{:min_id, min_id}, {:max_id, max_id}}
|
||||||
|
|
||||||
|
id = Phoenix.Controller.current_url(conn)
|
||||||
|
base_id = %{URI.parse(id) | query: nil} |> URI.to_string()
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"next" => current_url(conn, Map.put(params, :max_id, max_id)),
|
"next" => current_url(conn, Map.put(params, next_id, nid)),
|
||||||
"prev" => current_url(conn, Map.put(params, :min_id, min_id)),
|
"prev" => current_url(conn, Map.put(params, prev_id, pid)),
|
||||||
"id" => current_url(conn)
|
"id" => id,
|
||||||
|
"partOf" => base_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_pagination_fields(conn, entries, extra_params \\ %{}) do
|
defp get_first_last_pagination_id(entries) do
|
||||||
case List.last(entries) do
|
case List.last(entries) do
|
||||||
%{pagination_id: max_id} when not is_nil(max_id) ->
|
%{pagination_id: last_id} when not is_nil(last_id) ->
|
||||||
%{pagination_id: min_id} = List.first(entries)
|
%{pagination_id: first_id} = List.first(entries)
|
||||||
|
{first_id, last_id}
|
||||||
|
|
||||||
build_pagination_fields(conn, min_id, max_id, extra_params)
|
%{id: last_id} ->
|
||||||
|
%{id: first_id} = List.first(entries)
|
||||||
%{id: max_id} ->
|
{first_id, last_id}
|
||||||
%{id: min_id} = List.first(entries)
|
|
||||||
|
|
||||||
build_pagination_fields(conn, min_id, max_id, extra_params)
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
%{}
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_pagination_fields(conn, entries, extra_params \\ %{}, order \\ :desc)
|
||||||
|
|
||||||
|
def get_pagination_fields(conn, entries, extra_params, :desc) do
|
||||||
|
case get_first_last_pagination_id(entries) do
|
||||||
|
nil -> %{}
|
||||||
|
{min_id, max_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :desc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_pagination_fields(conn, entries, extra_params, :asc) do
|
||||||
|
case get_first_last_pagination_id(entries) do
|
||||||
|
nil -> %{}
|
||||||
|
{max_id, min_id} -> build_pagination_fields(conn, min_id, max_id, extra_params, :asc)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -972,6 +972,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
||||||
|
get("/objects/:uuid/replies", ActivityPubController, :object_replies)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,133 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/objects/:uuid/replies" do
|
||||||
|
test "it renders the top-level collection", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note_activity)
|
||||||
|
note = Pleroma.Activity.get_by_id_with_object(note.id)
|
||||||
|
uuid = String.split(note.object.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/replies")
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"id" => _,
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => 1,
|
||||||
|
"first" => %{
|
||||||
|
"id" => _,
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"orderedItems" => [_]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
json_response(conn, 200)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "first page id includes `?page=true`", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note_activity)
|
||||||
|
note = Pleroma.Activity.get_by_id_with_object(note.id)
|
||||||
|
uuid = String.split(note.object.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/replies")
|
||||||
|
|
||||||
|
%{"id" => collection_id, "first" => %{"id" => page_id, "partOf" => part_of}} =
|
||||||
|
json_response(conn, 200)
|
||||||
|
|
||||||
|
assert part_of == collection_id
|
||||||
|
assert String.contains?(page_id, "page=true")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unknown query params do not crash the endpoint", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note_activity)
|
||||||
|
note = Pleroma.Activity.get_by_id_with_object(note.id)
|
||||||
|
uuid = String.split(note.object.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/replies?unknown_param=1")
|
||||||
|
|
||||||
|
assert %{"type" => "OrderedCollection"} = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it renders a collection page", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
note = insert(:note_activity)
|
||||||
|
note = Pleroma.Activity.get_by_id_with_object(note.id)
|
||||||
|
uuid = String.split(note.object.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
|
{:ok, r1} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
{:ok, r2} =
|
||||||
|
CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user, %{status: "reply3", in_reply_to_status_id: note.id})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/replies?page=true&min_id=#{r1.object.id}&limit=1")
|
||||||
|
|
||||||
|
expected_uris = [r2.object.data["id"]]
|
||||||
|
|
||||||
|
assert match?(
|
||||||
|
%{
|
||||||
|
"id" => _,
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"prev" => _,
|
||||||
|
"next" => _,
|
||||||
|
"orderedItems" => ^expected_uris
|
||||||
|
},
|
||||||
|
json_response(conn, 200)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "/activities/:uuid" do
|
describe "/activities/:uuid" do
|
||||||
|
test "it does not include a top-level replies collection on activities", %{conn: conn} do
|
||||||
|
clear_config([:activitypub, :note_replies_output_limit], 1)
|
||||||
|
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
activity = Activity.get_by_id_with_object(activity.id)
|
||||||
|
|
||||||
|
uuid = String.split(activity.data["id"], "/") |> List.last()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/activities/#{uuid}")
|
||||||
|
|
||||||
|
res = json_response(conn, 200)
|
||||||
|
|
||||||
|
refute Map.has_key?(res, "replies")
|
||||||
|
assert get_in(res, ["object", "replies", "id"]) == activity.object.data["id"] <> "/replies"
|
||||||
|
end
|
||||||
|
|
||||||
test "it doesn't return a local-only activity", %{conn: conn} do
|
test "it doesn't return a local-only activity", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
|
{:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
|
||||||
|
|
|
||||||
|
|
@ -696,12 +696,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
describe "set_replies/1" do
|
describe "set_replies/1" do
|
||||||
setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
|
setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
|
||||||
|
|
||||||
test "returns unmodified object if activity doesn't have self-replies" do
|
test "still provides reply collection id even if activity doesn't have replies yet" do
|
||||||
data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
|
data = Jason.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
|
||||||
assert Transmogrifier.set_replies(data) == data
|
object = data["object"] |> Map.delete("replies")
|
||||||
|
modified = Transmogrifier.set_replies(object)
|
||||||
|
|
||||||
|
refute object["replies"]
|
||||||
|
assert modified["replies"]
|
||||||
|
assert match?(%{"id" => "http" <> _, "totalItems" => 0}, modified["replies"])
|
||||||
|
# first page should be omitted if there are no entries anyway
|
||||||
|
refute modified["replies"]["first"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sets `replies` collection with a limited number of self-replies" do
|
test "sets `replies` collection with a limited number of replies, preferring oldest" do
|
||||||
[user, another_user] = insert_list(2, :user)
|
[user, another_user] = insert_list(2, :user)
|
||||||
|
|
||||||
{:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
|
{:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
|
||||||
|
|
@ -730,7 +737,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
object = Object.normalize(activity, fetch: false)
|
object = Object.normalize(activity, fetch: false)
|
||||||
replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
|
replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
|
||||||
|
|
||||||
assert %{"type" => "Collection", "items" => ^replies_uris} =
|
assert %{"type" => "OrderedCollection", "first" => %{"orderedItems" => ^replies_uris}} =
|
||||||
Transmogrifier.set_replies(object.data)["replies"]
|
Transmogrifier.set_replies(object.data)["replies"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,39 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
||||||
replies_uris = [self_reply1.object.data["id"]]
|
replies_uris = [self_reply1.object.data["id"]]
|
||||||
result = ObjectView.render("object.json", %{object: refresh_record(activity)})
|
result = ObjectView.render("object.json", %{object: refresh_record(activity)})
|
||||||
|
|
||||||
assert %{"type" => "Collection", "items" => ^replies_uris} =
|
assert %{
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"id" => _,
|
||||||
|
"first" => %{"orderedItems" => ^replies_uris}
|
||||||
|
} =
|
||||||
get_in(result, ["object", "replies"])
|
get_in(result, ["object", "replies"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "renders a replies collection on its own" do
|
||||||
|
user = insert(:user)
|
||||||
|
activity = insert(:note_activity, user: user)
|
||||||
|
activity = Pleroma.Activity.get_by_id_with_object(activity.id)
|
||||||
|
|
||||||
|
{:ok, r1} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
|
||||||
|
|
||||||
|
{:ok, r2} =
|
||||||
|
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
|
||||||
|
|
||||||
|
replies_uris = [r1.object.data["id"], r2.object.data["id"]]
|
||||||
|
|
||||||
|
result =
|
||||||
|
ObjectView.render("object_replies.json", %{
|
||||||
|
render_params: %{object_ap_id: activity.object.data["id"]}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"id" => _,
|
||||||
|
"totalItems" => 2,
|
||||||
|
"first" => %{"orderedItems" => ^replies_uris}
|
||||||
|
} = result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "renders a like activity" do
|
test "renders a like activity" do
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,18 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||||
user = Map.merge(user, %{hide_followers_count: false, hide_followers: true})
|
user = Map.merge(user, %{hide_followers_count: false, hide_followers: true})
|
||||||
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not hide follower items based on `hide_follows`" do
|
||||||
|
user = insert(:user)
|
||||||
|
follower = insert(:user)
|
||||||
|
{:ok, user, _follower, _activity} = CommonAPI.follow(user, follower)
|
||||||
|
|
||||||
|
user = Map.merge(user, %{hide_followers: false, hide_follows: true})
|
||||||
|
follower_ap_id = follower.ap_id
|
||||||
|
|
||||||
|
assert %{"first" => %{"orderedItems" => [^follower_ap_id]}} =
|
||||||
|
UserView.render("followers.json", %{user: user})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "following" do
|
describe "following" do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue