forked from AkkomaGang/akkoma
Merge branch 'replies-count' into 'develop'
Increment replies_count (MastoAPI) Closes #756 See merge request pleroma/pleroma!974
This commit is contained in:
commit
21ff78cd40
6 changed files with 235 additions and 1 deletions
|
@ -245,4 +245,50 @@ def all_by_actor_and_id(actor, status_ids) do
|
||||||
|> where([s], s.actor == ^actor)
|
|> where([s], s.actor == ^actor)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def increase_replies_count(id) do
|
||||||
|
Activity
|
||||||
|
|> where(id: ^id)
|
||||||
|
|> update([a],
|
||||||
|
set: [
|
||||||
|
data:
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
jsonb_set(?, '{object, repliesCount}',
|
||||||
|
(coalesce((?->'object'->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
|
||||||
|
""",
|
||||||
|
a.data,
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|> case do
|
||||||
|
{1, [activity]} -> activity
|
||||||
|
_ -> {:error, "Not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrease_replies_count(id) do
|
||||||
|
Activity
|
||||||
|
|> where(id: ^id)
|
||||||
|
|> update([a],
|
||||||
|
set: [
|
||||||
|
data:
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
jsonb_set(?, '{object, repliesCount}',
|
||||||
|
(greatest(0, (?->'object'->>'repliesCount')::int - 1))::varchar::jsonb, true)
|
||||||
|
""",
|
||||||
|
a.data,
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|> case do
|
||||||
|
{1, [activity]} -> activity
|
||||||
|
_ -> {:error, "Not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -133,4 +133,50 @@ def update_and_set_cache(changeset) do
|
||||||
e -> e
|
e -> e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def increase_replies_count(ap_id) do
|
||||||
|
Object
|
||||||
|
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
||||||
|
|> update([o],
|
||||||
|
set: [
|
||||||
|
data:
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
jsonb_set(?, '{repliesCount}',
|
||||||
|
(coalesce((?->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
|
||||||
|
""",
|
||||||
|
o.data,
|
||||||
|
o.data
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|> case do
|
||||||
|
{1, [object]} -> set_cache(object)
|
||||||
|
_ -> {:error, "Not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrease_replies_count(ap_id) do
|
||||||
|
Object
|
||||||
|
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
||||||
|
|> update([o],
|
||||||
|
set: [
|
||||||
|
data:
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
jsonb_set(?, '{repliesCount}',
|
||||||
|
(greatest(0, (?->>'repliesCount')::int - 1))::varchar::jsonb, true)
|
||||||
|
""",
|
||||||
|
o.data,
|
||||||
|
o.data
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|> case do
|
||||||
|
{1, [object]} -> set_cache(object)
|
||||||
|
_ -> {:error, "Not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -89,6 +89,30 @@ def decrease_note_count_if_public(actor, object) do
|
||||||
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
|
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def increase_replies_count_if_reply(%{
|
||||||
|
"object" =>
|
||||||
|
%{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object,
|
||||||
|
"type" => "Create"
|
||||||
|
}) do
|
||||||
|
if is_public?(object) do
|
||||||
|
Activity.increase_replies_count(reply_status_id)
|
||||||
|
Object.increase_replies_count(reply_ap_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def increase_replies_count_if_reply(_create_data), do: :noop
|
||||||
|
|
||||||
|
def decrease_replies_count_if_reply(%Object{
|
||||||
|
data: %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object
|
||||||
|
}) do
|
||||||
|
if is_public?(object) do
|
||||||
|
Activity.decrease_replies_count(reply_status_id)
|
||||||
|
Object.decrease_replies_count(reply_ap_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrease_replies_count_if_reply(_object), do: :noop
|
||||||
|
|
||||||
def insert(map, local \\ true) when is_map(map) do
|
def insert(map, local \\ true) when is_map(map) do
|
||||||
with nil <- Activity.normalize(map),
|
with nil <- Activity.normalize(map),
|
||||||
map <- lazy_put_activity_defaults(map),
|
map <- lazy_put_activity_defaults(map),
|
||||||
|
@ -178,6 +202,7 @@ def create(%{to: to, actor: actor, context: context, object: object} = params) d
|
||||||
additional
|
additional
|
||||||
),
|
),
|
||||||
{:ok, activity} <- insert(create_data, local),
|
{:ok, activity} <- insert(create_data, local),
|
||||||
|
_ <- increase_replies_count_if_reply(create_data),
|
||||||
# Changing note count prior to enqueuing federation task in order to avoid
|
# Changing note count prior to enqueuing federation task in order to avoid
|
||||||
# race conditions on updating user.info
|
# race conditions on updating user.info
|
||||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||||
|
@ -329,6 +354,7 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
|
||||||
"deleted_activity_id" => activity && activity.id
|
"deleted_activity_id" => activity && activity.id
|
||||||
},
|
},
|
||||||
{:ok, activity} <- insert(data, local),
|
{:ok, activity} <- insert(data, local),
|
||||||
|
_ <- decrease_replies_count_if_reply(object),
|
||||||
# Changing note count prior to enqueuing federation task in order to avoid
|
# Changing note count prior to enqueuing federation task in order to avoid
|
||||||
# race conditions on updating user.info
|
# race conditions on updating user.info
|
||||||
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
||||||
|
|
|
@ -174,7 +174,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
content: content,
|
content: content,
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
reblogs_count: announcement_count,
|
reblogs_count: announcement_count,
|
||||||
replies_count: 0,
|
replies_count: object["repliesCount"] || 0,
|
||||||
favourites_count: like_count,
|
favourites_count: like_count,
|
||||||
reblogged: present?(repeated),
|
reblogged: present?(repeated),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UpdateStatusReplyCount do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("""
|
||||||
|
WITH reply_count AS (
|
||||||
|
SELECT count(*) AS count, data->>'inReplyTo' AS ap_id
|
||||||
|
FROM objects
|
||||||
|
WHERE
|
||||||
|
data->>'inReplyTo' IS NOT NULL AND
|
||||||
|
data->>'type' = 'Note' AND (
|
||||||
|
data->'cc' ? '#{@public}' OR
|
||||||
|
data->'to' ? '#{@public}')
|
||||||
|
GROUP BY data->>'inReplyTo'
|
||||||
|
)
|
||||||
|
UPDATE objects AS o
|
||||||
|
SET "data" = jsonb_set(o.data, '{repliesCount}', reply_count.count::varchar::jsonb, true)
|
||||||
|
FROM reply_count
|
||||||
|
WHERE reply_count.ap_id = o.data->>'id';
|
||||||
|
""")
|
||||||
|
|
||||||
|
execute("""
|
||||||
|
WITH reply_count AS (SELECT
|
||||||
|
count(*) as count,
|
||||||
|
data->'object'->>'inReplyTo' AS ap_id
|
||||||
|
FROM
|
||||||
|
activities
|
||||||
|
WHERE
|
||||||
|
data->'object'->>'inReplyTo' IS NOT NULL AND
|
||||||
|
data->'object'->>'type' = 'Note' AND (
|
||||||
|
data->'object'->'cc' ? '#{@public}' OR
|
||||||
|
data->'object'->'to' ? '#{@public}')
|
||||||
|
GROUP BY
|
||||||
|
data->'object'->>'inReplyTo'
|
||||||
|
)
|
||||||
|
UPDATE activities AS a
|
||||||
|
SET "data" = jsonb_set(a.data, '{object, repliesCount}', reply_count.count::varchar::jsonb, true)
|
||||||
|
FROM reply_count
|
||||||
|
WHERE reply_count.ap_id = a.data->'object'->>'id';
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
:noop
|
||||||
|
end
|
||||||
|
end
|
|
@ -232,6 +232,39 @@ test "increases user note count only for public activities" do
|
||||||
user = Repo.get(User, user.id)
|
user = Repo.get(User, user.id)
|
||||||
assert user.info.note_count == 2
|
assert user.info.note_count == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "increases replies count" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
|
||||||
|
ap_id = activity.data["id"]
|
||||||
|
reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
|
||||||
|
|
||||||
|
# public
|
||||||
|
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 1
|
||||||
|
assert object.data["repliesCount"] == 1
|
||||||
|
|
||||||
|
# unlisted
|
||||||
|
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 2
|
||||||
|
assert object.data["repliesCount"] == 2
|
||||||
|
|
||||||
|
# private
|
||||||
|
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 2
|
||||||
|
assert object.data["repliesCount"] == 2
|
||||||
|
|
||||||
|
# direct
|
||||||
|
{:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 2
|
||||||
|
assert object.data["repliesCount"] == 2
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "fetch activities for recipients" do
|
describe "fetch activities for recipients" do
|
||||||
|
@ -751,6 +784,40 @@ test "it creates a delete activity and checks that it is also sent to users ment
|
||||||
|
|
||||||
assert user.ap_id in delete.data["to"]
|
assert user.ap_id in delete.data["to"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "decreases reply count" do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
|
||||||
|
reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
|
||||||
|
ap_id = activity.data["id"]
|
||||||
|
|
||||||
|
{:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
|
||||||
|
{:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
|
||||||
|
{:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
|
||||||
|
{:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
|
||||||
|
|
||||||
|
_ = CommonAPI.delete(direct_reply.id, user2)
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 2
|
||||||
|
assert object.data["repliesCount"] == 2
|
||||||
|
|
||||||
|
_ = CommonAPI.delete(private_reply.id, user2)
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 2
|
||||||
|
assert object.data["repliesCount"] == 2
|
||||||
|
|
||||||
|
_ = CommonAPI.delete(public_reply.id, user2)
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 1
|
||||||
|
assert object.data["repliesCount"] == 1
|
||||||
|
|
||||||
|
_ = CommonAPI.delete(unlisted_reply.id, user2)
|
||||||
|
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
||||||
|
assert data["object"]["repliesCount"] == 0
|
||||||
|
assert object.data["repliesCount"] == 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "timeline post-processing" do
|
describe "timeline post-processing" do
|
||||||
|
@ -789,6 +856,7 @@ test "it filters broken threads" do
|
||||||
|
|
||||||
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
|
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
|
||||||
|
|
||||||
|
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
|
||||||
assert [public_activity, private_activity_1, private_activity_3] == activities
|
assert [public_activity, private_activity_1, private_activity_3] == activities
|
||||||
assert length(activities) == 3
|
assert length(activities) == 3
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue