Merge pull request 'hashtag-timeline-fix' (#7938) from mkljczk/pleroma:hashtag-timeline-fix into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/7938
This commit is contained in:
lain 2026-07-24 15:29:25 +00:00
commit a790e08a6c
3 changed files with 58 additions and 17 deletions

View file

@ -0,0 +1 @@
Fix hashtag timeline query for additional tag handling

View file

@ -893,14 +893,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Repo.all()
# Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan
from(
[_activity, object] in query,
join: hto in "hashtags_objects",
on: hto.object_id == object.id,
where: hto.hashtag_id in ^hashtag_ids,
distinct: [desc: object.id],
order_by: [desc: object.id]
)
query =
from(
[_activity, object] in query,
join: hto in "hashtags_objects",
on: hto.object_id == object.id,
where: hto.hashtag_id in ^hashtag_ids
)
if is_nil(query.distinct) do
from([_activity, object] in query,
distinct: [desc: object.id],
order_by: [desc: object.id]
)
else
query
end
end
defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do

View file

@ -907,21 +907,53 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
{:ok, activity_test1} = CommonAPI.post(user, %{status: "#test #test1"})
{:ok, activity_none} = CommonAPI.post(user, %{status: "#test #none"})
any_test = get(conn, "/api/v1/timelines/tag/test?any[]=test1")
for hashtag_timeline_mode <- [:enabled, :disabled] do
clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_mode)
[status_none, status_test1, status_test] = json_response_and_validate_schema(any_test, :ok)
any_test = get(conn, "/api/v1/timelines/tag/test?any[]=test1")
assert to_string(activity_test.id) == status_test["id"]
assert to_string(activity_test1.id) == status_test1["id"]
assert to_string(activity_none.id) == status_none["id"]
[status_none, status_test1, status_test] =
json_response_and_validate_schema(any_test, :ok)
restricted_test = get(conn, "/api/v1/timelines/tag/test?all[]=test1&none[]=none")
assert to_string(activity_test.id) == status_test["id"]
assert to_string(activity_test1.id) == status_test1["id"]
assert to_string(activity_none.id) == status_none["id"]
assert [status_test1] == json_response_and_validate_schema(restricted_test, :ok)
restricted_test = get(conn, "/api/v1/timelines/tag/test?all[]=test1&none[]=none")
all_test = get(conn, "/api/v1/timelines/tag/test?all[]=none")
assert [status_test1] == json_response_and_validate_schema(restricted_test, :ok)
assert [status_none] == json_response_and_validate_schema(all_test, :ok)
all_test = get(conn, "/api/v1/timelines/tag/test?all[]=none")
assert [status_none] == json_response_and_validate_schema(all_test, :ok)
end
end
test "multi-hashtag timeline pagination returns unique statuses", %{conn: conn} do
user = insert(:user)
{:ok, activity_one} = CommonAPI.post(user, %{status: "#test #test1"})
{:ok, activity_two} = CommonAPI.post(user, %{status: "#test #test1"})
{:ok, activity_three} = CommonAPI.post(user, %{status: "#test #test1"})
for hashtag_timeline_mode <- [:enabled, :disabled] do
clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_mode)
first_page =
conn
|> get("/api/v1/timelines/tag/test?any[]=test1&limit=2")
|> json_response_and_validate_schema(:ok)
assert Enum.map(first_page, & &1["id"]) ==
Enum.map([activity_three, activity_two], &to_string(&1.id))
second_page =
conn
|> get("/api/v1/timelines/tag/test?any[]=test1&limit=2&max_id=#{activity_two.id}")
|> json_response_and_validate_schema(:ok)
assert Enum.map(second_page, & &1["id"]) == [to_string(activity_one.id)]
end
end
test "muted emotions", %{conn: conn} do