Fix for feed page pagination Closes #1605 See merge request pleroma/pleroma!2281feature/add-subject-to-text-search
@@ -87,7 +87,8 @@ defmodule Pleroma.Web.ControllerHelper do | |||
render_error(conn, :not_implemented, "Can't display this activity") | |||
end | |||
@spec put_in_if_exist(map(), atom() | String.t(), any) :: map() | |||
def put_in_if_exist(map, _key, nil), do: map | |||
def put_in_if_exist(map, key, value), do: put_in(map, key, value) | |||
@spec put_if_exist(map(), atom() | String.t(), any) :: map() | |||
def put_if_exist(map, _key, nil), do: map | |||
def put_if_exist(map, key, value), do: Map.put(map, key, value) | |||
end |
@@ -9,18 +9,18 @@ defmodule Pleroma.Web.Feed.TagController do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.Feed.FeedView | |||
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] | |||
import Pleroma.Web.ControllerHelper, only: [put_if_exist: 3] | |||
def feed(conn, %{"tag" => raw_tag} = params) do | |||
{format, tag} = parse_tag(raw_tag) | |||
activities = | |||
%{"type" => ["Create"], "tag" => tag} | |||
|> put_in_if_exist("max_id", params["max_id"]) | |||
|> put_if_exist("max_id", params["max_id"]) | |||
|> ActivityPub.fetch_public_activities() | |||
conn | |||
|> put_resp_content_type("application/atom+xml") | |||
|> put_resp_content_type("application/#{format}+xml") | |||
|> put_view(FeedView) | |||
|> render("tag.#{format}", | |||
activities: activities, | |||
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.Feed.UserController do | |||
alias Pleroma.Web.ActivityPub.ActivityPubController | |||
alias Pleroma.Web.Feed.FeedView | |||
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] | |||
import Pleroma.Web.ControllerHelper, only: [put_if_exist: 3] | |||
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) | |||
@@ -40,19 +40,28 @@ defmodule Pleroma.Web.Feed.UserController do | |||
end | |||
def feed(conn, %{"nickname" => nickname} = params) do | |||
format = get_format(conn) | |||
format = | |||
if format in ["rss", "atom"] do | |||
format | |||
else | |||
"atom" | |||
end | |||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do | |||
activities = | |||
%{ | |||
"type" => ["Create"], | |||
"actor_id" => user.ap_id | |||
} | |||
|> put_in_if_exist("max_id", params["max_id"]) | |||
|> put_if_exist("max_id", params["max_id"]) | |||
|> ActivityPub.fetch_public_activities() | |||
conn | |||
|> put_resp_content_type("application/atom+xml") | |||
|> put_resp_content_type("application/#{format}+xml") | |||
|> put_view(FeedView) | |||
|> render("user.xml", | |||
|> render("user.#{format}", | |||
user: user, | |||
activities: activities, | |||
feed_config: Pleroma.Config.get([:feed]) | |||
@@ -513,7 +513,7 @@ defmodule Pleroma.Web.Router do | |||
end | |||
pipeline :ostatus do | |||
plug(:accepts, ["html", "xml", "atom", "activity+json", "json"]) | |||
plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) | |||
plug(Pleroma.Plugs.StaticFEPlug) | |||
end | |||
@@ -0,0 +1,49 @@ | |||
<item> | |||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> | |||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> | |||
<guid><%= @data["id"] %></guid> | |||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title> | |||
<description><%= activity_content(@object) %></description> | |||
<pubDate><%= @data["published"] %></pubDate> | |||
<updated><%= @data["published"] %></updated> | |||
<ostatus:conversation ref="<%= activity_context(@activity) %>"> | |||
<%= activity_context(@activity) %> | |||
</ostatus:conversation> | |||
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link> | |||
<%= if @data["summary"] do %> | |||
<description><%= @data["summary"] %></description> | |||
<% end %> | |||
<%= if @activity.local do %> | |||
<link><%= @data["id"] %></link> | |||
<% else %> | |||
<link><%= @data["external_url"] %></link> | |||
<% end %> | |||
<%= for tag <- @data["tag"] || [] do %> | |||
<category term="<%= tag %>"></category> | |||
<% end %> | |||
<%= for attachment <- @data["attachment"] || [] do %> | |||
<link type="<%= attachment_type(attachment) %>"><%= attachment_href(attachment) %></link> | |||
<% end %> | |||
<%= if @data["inReplyTo"] do %> | |||
<thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/> | |||
<% end %> | |||
<%= for id <- @activity.recipients do %> | |||
<%= if id == Pleroma.Constants.as_public() do %> | |||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection">http://activityschema.org/collection/public</link> | |||
<% else %> | |||
<%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> | |||
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"><%= id %></link> | |||
<% end %> | |||
<% end %> | |||
<% end %> | |||
<%= for {emoji, file} <- @data["emoji"] || %{} do %> | |||
<link name="<%= emoji %>" rel="emoji"><%= file %></link> | |||
<% end %> | |||
</item> |
@@ -0,0 +1,17 @@ | |||
<managingEditor> | |||
<guid><%= @user.ap_id %></guid> | |||
<activity:object>http://activitystrea.ms/schema/1.0/person</activity:object> | |||
<uri><%= @user.ap_id %></uri> | |||
<poco:preferredUsername><%= @user.nickname %></poco:preferredUsername> | |||
<poco:displayName><%= @user.name %></poco:displayName> | |||
<poco:note><%= escape(@user.bio) %></poco:note> | |||
<description><%= escape(@user.bio) %></description> | |||
<name><%= @user.nickname %></name> | |||
<link rel="avatar"><%= User.avatar_url(@user) %></link> | |||
<%= if User.banner_url(@user) do %> | |||
<link rel="header"><%= User.banner_url(@user) %></link> | |||
<% end %> | |||
<%= if @user.local do %> | |||
<ap_enabled>true</ap_enabled> | |||
<% end %> | |||
</managingEditor> |
@@ -12,13 +12,13 @@ | |||
<logo><%= logo(@user) %></logo> | |||
<link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/> | |||
<%= render @view_module, "_author.xml", assigns %> | |||
<%= render @view_module, "_author.atom", assigns %> | |||
<%= if last_activity(@activities) do %> | |||
<link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/> | |||
<% end %> | |||
<%= for activity <- @activities do %> | |||
<%= render @view_module, "_activity.xml", Map.merge(assigns, prepare_activity(activity)) %> | |||
<%= render @view_module, "_activity.atom", Map.merge(assigns, prepare_activity(activity)) %> | |||
<% end %> | |||
</feed> |
@@ -0,0 +1,20 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<rss version="2.0"> | |||
<channel> | |||
<guid><%= user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid> | |||
<title><%= @user.nickname <> "'s timeline" %></title> | |||
<updated><%= most_recent_update(@activities, @user) %></updated> | |||
<image><%= logo(@user) %></image> | |||
<link><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link> | |||
<%= render @view_module, "_author.rss", assigns %> | |||
<%= if last_activity(@activities) do %> | |||
<link rel="next"><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link> | |||
<% end %> | |||
<%= for activity <- @activities do %> | |||
<%= render @view_module, "_activity.rss", Map.merge(assigns, prepare_activity(activity)) %> | |||
<% end %> | |||
</channel> | |||
</rss> |
@@ -8,6 +8,8 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
import Pleroma.Factory | |||
import SweetXml | |||
alias Pleroma.Object | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.Feed.FeedView | |||
setup do: clear_config([:feed]) | |||
@@ -19,9 +21,9 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
) | |||
user = insert(:user) | |||
{:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) | |||
{:ok, activity1} = CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) | |||
object = Pleroma.Object.normalize(activity1) | |||
object = Object.normalize(activity1) | |||
object_data = | |||
Map.put(object.data, "attachment", [ | |||
@@ -41,14 +43,13 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
|> Ecto.Changeset.change(data: object_data) | |||
|> Pleroma.Repo.update() | |||
{:ok, _activity2} = | |||
Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) | |||
{:ok, activity2} = CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) | |||
{:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) | |||
{:ok, _activity3} = CommonAPI.post(user, %{"status" => "This is :moominmamma"}) | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/atom+xml") | |||
|> put_req_header("accept", "application/atom+xml") | |||
|> get(tag_feed_path(conn, :feed, "pleromaart.atom")) | |||
|> response(200) | |||
@@ -63,6 +64,21 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
assert xpath(xml, ~x"//feed/entry/author/name/text()"ls) == [user.nickname, user.nickname] | |||
assert xpath(xml, ~x"//feed/entry/author/id/text()"ls) == [user.ap_id, user.ap_id] | |||
conn = | |||
conn | |||
|> put_req_header("accept", "application/atom+xml") | |||
|> get("/tags/pleromaart.atom", %{"max_id" => activity2.id}) | |||
assert get_resp_header(conn, "content-type") == ["application/atom+xml; charset=utf-8"] | |||
resp = response(conn, 200) | |||
xml = parse(resp) | |||
assert xpath(xml, ~x"//feed/title/text()") == '#pleromaart' | |||
assert xpath(xml, ~x"//feed/entry/title/text()"l) == [ | |||
'yeah #PleromaArt' | |||
] | |||
end | |||
test "gets a feed (RSS)", %{conn: conn} do | |||
@@ -72,9 +88,9 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
) | |||
user = insert(:user) | |||
{:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) | |||
{:ok, activity1} = CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) | |||
object = Pleroma.Object.normalize(activity1) | |||
object = Object.normalize(activity1) | |||
object_data = | |||
Map.put(object.data, "attachment", [ | |||
@@ -94,14 +110,13 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
|> Ecto.Changeset.change(data: object_data) | |||
|> Pleroma.Repo.update() | |||
{:ok, activity2} = | |||
Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) | |||
{:ok, activity2} = CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) | |||
{:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) | |||
{:ok, _activity3} = CommonAPI.post(user, %{"status" => "This is :moominmamma"}) | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/rss+xml") | |||
|> put_req_header("accept", "application/rss+xml") | |||
|> get(tag_feed_path(conn, :feed, "pleromaart.rss")) | |||
|> response(200) | |||
@@ -131,8 +146,8 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4" | |||
] | |||
obj1 = Pleroma.Object.normalize(activity1) | |||
obj2 = Pleroma.Object.normalize(activity2) | |||
obj1 = Object.normalize(activity1) | |||
obj2 = Object.normalize(activity2) | |||
assert xpath(xml, ~x"//channel/item/description/text()"sl) == [ | |||
HtmlEntities.decode(FeedView.activity_content(obj2)), | |||
@@ -141,7 +156,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/atom+xml") | |||
|> put_req_header("accept", "application/rss+xml") | |||
|> get(tag_feed_path(conn, :feed, "pleromaart")) | |||
|> response(200) | |||
@@ -150,5 +165,20 @@ defmodule Pleroma.Web.Feed.TagControllerTest do | |||
assert xpath(xml, ~x"//channel/description/text()"s) == | |||
"These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse." | |||
conn = | |||
conn | |||
|> put_req_header("accept", "application/rss+xml") | |||
|> get("/tags/pleromaart.rss", %{"max_id" => activity2.id}) | |||
assert get_resp_header(conn, "content-type") == ["application/rss+xml; charset=utf-8"] | |||
resp = response(conn, 200) | |||
xml = parse(resp) | |||
assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart' | |||
assert xpath(xml, ~x"//channel/item/title/text()"l) == [ | |||
'yeah #PleromaArt' | |||
] | |||
end | |||
end |
@@ -52,12 +52,12 @@ defmodule Pleroma.Web.Feed.UserControllerTest do | |||
} | |||
) | |||
_note_activity2 = insert(:note_activity, note: note2) | |||
note_activity2 = insert(:note_activity, note: note2) | |||
object = Object.normalize(note_activity) | |||
resp = | |||
conn | |||
|> put_req_header("content-type", "application/atom+xml") | |||
|> put_req_header("accept", "application/atom+xml") | |||
|> get(user_feed_path(conn, :feed, user.nickname)) | |||
|> response(200) | |||
@@ -68,12 +68,91 @@ defmodule Pleroma.Web.Feed.UserControllerTest do | |||
assert activity_titles == ['42 This...', 'This is...'] | |||
assert resp =~ object.data["content"] | |||
resp = | |||
conn | |||
|> put_req_header("accept", "application/atom+xml") | |||
|> get("/users/#{user.nickname}/feed", %{"max_id" => note_activity2.id}) | |||
|> response(200) | |||
activity_titles = | |||
resp | |||
|> SweetXml.parse() | |||
|> SweetXml.xpath(~x"//entry/title/text()"l) | |||
assert activity_titles == ['This is...'] | |||
end | |||
test "gets a rss feed", %{conn: conn} do | |||
Pleroma.Config.put( | |||
[:feed, :post_title], | |||
%{max_length: 10, omission: "..."} | |||
) | |||
activity = insert(:note_activity) | |||
note = | |||
insert(:note, | |||
data: %{ | |||
"content" => "This is :moominmamma: note ", | |||
"attachment" => [ | |||
%{ | |||
"url" => [ | |||
%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"} | |||
] | |||
} | |||
], | |||
"inReplyTo" => activity.data["id"] | |||
} | |||
) | |||
note_activity = insert(:note_activity, note: note) | |||
user = User.get_cached_by_ap_id(note_activity.data["actor"]) | |||
note2 = | |||
insert(:note, | |||
user: user, | |||
data: %{ | |||
"content" => "42 This is :moominmamma: note ", | |||
"inReplyTo" => activity.data["id"] | |||
} | |||
) | |||
note_activity2 = insert(:note_activity, note: note2) | |||
object = Object.normalize(note_activity) | |||
resp = | |||
conn | |||
|> put_req_header("accept", "application/rss+xml") | |||
|> get("/users/#{user.nickname}/feed.rss") | |||
|> response(200) | |||
activity_titles = | |||
resp | |||
|> SweetXml.parse() | |||
|> SweetXml.xpath(~x"//item/title/text()"l) | |||
assert activity_titles == ['42 This...', 'This is...'] | |||
assert resp =~ object.data["content"] | |||
resp = | |||
conn | |||
|> put_req_header("accept", "application/rss+xml") | |||
|> get("/users/#{user.nickname}/feed.rss", %{"max_id" => note_activity2.id}) | |||
|> response(200) | |||
activity_titles = | |||
resp | |||
|> SweetXml.parse() | |||
|> SweetXml.xpath(~x"//item/title/text()"l) | |||
assert activity_titles == ['This is...'] | |||
end | |||
test "returns 404 for a missing feed", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/atom+xml") | |||
|> put_req_header("accept", "application/atom+xml") | |||
|> get(user_feed_path(conn, :feed, "nonexisting")) | |||
assert response(conn, 404) | |||