Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support RSS feeds for specific topics (essentially combining current /feed and /topic functionality) #430

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions lib/changelog_web/controllers/feed_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,80 @@ defmodule ChangelogWeb.FeedController do
|> render("sitemap.xml")
end

@doc "A topic's news and podcasts feed"
def topic(conn, %{"slug" => slug}) do
topic = Repo.get_by!(Topic, slug: slug)

items =
topic
|> filter_items_by_topic(:all)
|> fetch_items_by_topic()

render_topic(conn, slug, items)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

render_topic(conn, topic, items) 🐛

end

@doc "A topic's news feed"
def topic_news(conn, %{"slug" => slug}) do
topic = Repo.get_by!(Topic, slug: slug)

news_items =
topic
|> filter_items_by_topic(:news)
|> fetch_items_by_topic()

render_topic(conn, topic, news_items)
end

@doc "A topic's podcasts feed"
def topic_podcasts(conn, %{"slug" => slug}) do
topic = Repo.get_by!(Topic, slug: slug)

podcasts =
topic
|> filter_items_by_topic(:podcasts)
|> fetch_items_by_topic()

render_topic(conn, topic, podcasts)
end

defp filter_items_by_topic(topic, :all) do
NewsItem
|> NewsItem.with_topic(topic)
end

defp filter_items_by_topic(topic, :news) do
NewsItem
|> NewsItem.with_topic(topic)
|> NewsItem.non_audio()
end

defp filter_items_by_topic(topic, :podcasts) do
NewsItem
|> NewsItem.with_topic(topic)
|> NewsItem.audio()
end

defp fetch_items_by_topic(query) do
query
|> NewsItem.published()
|> NewsItem.newest_first()
|> NewsItem.preload_all()
|> Repo.all()
end

defp render_topic(conn, topic, items) do
IO.inspect(topic, label: "TOPIC")
IO.inspect(items, label: "ITEMS")

conn
|> put_layout(false)
|> put_resp_content_type("application/xml")
|> assign(:topic_name, topic.name)
|> assign(:topic_description, topic.description)
|> assign(:items, items)
|> ResponseCache.cache_public(cache_duration())
|> render("topic.xml")
end

defp cache_duration, do: 2..10 |> Enum.random() |> :timer.minutes()
end
14 changes: 12 additions & 2 deletions lib/changelog_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ defmodule ChangelogWeb.Router do
post "/episodes/:id/transcript", EpisodeController, :transcript, as: :episode

resources "/episode_requests", EpisodeRequestController
put "/episode_requests/:id/decline", EpisodeRequestController, :decline, as: :episode_request

put "/episode_requests/:id/decline", EpisodeRequestController, :decline,
as: :episode_request

put "/episode_requests/:id/fail", EpisodeRequestController, :fail, as: :episode_request
put "/episode_requests/:id/pend", EpisodeRequestController, :pend, as: :episode_request

Expand Down Expand Up @@ -141,11 +144,18 @@ defmodule ChangelogWeb.Router do
scope "/", ChangelogWeb do
pipe_through [:feed]

get "/sitemap.xml", FeedController, :sitemap

get "/feed", FeedController, :news
get "/feed/titles", FeedController, :news_titles

get "/topic/:slug/feed", FeedController, :topic_show
get "/topic/:slug/news/feed", FeedController, :topic_news
get "/topic/:slug/podcasts/feed", FeedController, :topic_podcasts

get "/posts/feed", FeedController, :posts
get "/sitemap.xml", FeedController, :sitemap
get "/:slug/feed", FeedController, :podcast

get "/plusplus/:slug/feed", FeedController, :plusplus
get "/metacast/:slug/feed", FeedController, :metacast
end
Expand Down
17 changes: 17 additions & 0 deletions lib/changelog_web/templates/feed/topic.xml.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<title>Changelog: <%= @topic_name %></title>
<copyright>All rights reserved</copyright>
<link><%= Routes.root_url(@conn, :index) %></link>
<atom:link href="<%= Routes.feed_url(@conn, :news) %>" rel="self" type="application/rss+xml" />
<atom:link href="<%= Routes.root_url(@conn, :index) %>" rel="alternate" type="text/html" />
<language>en-us</language>
<description><%= @topic_description %></description>
<%= for item <- @items do %>
<item>
<%= render_item(item, assigns) %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern matches to def render_item(item = %{object: nil}, assigns) do ... because a NewsItem struct is returned. Maybe the query should be changed so that it returns only the preloaded posts of the specific topic. wdyt? 🤔

</item>
<% end %>
</channel>
</rss>
89 changes: 89 additions & 0 deletions test/changelog_web/controllers/feed_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,93 @@ defmodule ChangelogWeb.FeedControllerTest do
refute conn.resp_body =~ p2.title
assert conn.resp_body =~ p3.title
end

describe "Topic feed" do
setup [:setup_topic_feed_tests]

test "the topic feed", %{
conn: conn,
topics: topics,
news_items_with_topics: news_items_with_topics,
posts_with_topics: posts_with_topics,
podcast_episodes_with_topics: podcast_episodes_with_topics
} do
topic_slug = "rust"
conn = get(conn, Routes.feed_path(conn, :topic, topic_slug))

assert conn.status == 200
assert valid_xml(conn)
end

test "the topic's news feed", %{
conn: conn,
topics: topics,
news_items_with_topics: news_items_with_topics,
posts_with_topics: posts_with_topics,
podcast_episodes_with_topics: podcast_episodes_with_topics
} do
topic_slug = "rust"
conn = get(conn, Routes.feed_path(conn, :topic_news, topic_slug))

assert conn.status == 200
assert valid_xml(conn)
end

test "the topic's podcasts feed", %{
conn: conn,
topics: topics,
news_items_with_topics: news_items_with_topics,
posts_with_topics: posts_with_topics,
podcast_episodes_with_topics: podcast_episodes_with_topics
} do
topic_slug = "rust"
conn = get(conn, Routes.feed_path(conn, :topic_podcasts, topic_slug))

assert conn.status == 200
assert valid_xml(conn)

topic = topics[topic_slug]
assert conn.resp_body =~ topic.name
assert conn.resp_body =~ topic.description

Enum.each(posts_with_topics, fn post_with_topic ->
assert conn.resp_body =~ post_with_topic.post.title
assert conn.resp_body =~ post_with_topic.post.body
end)

Enum.each(podcast_episodes_with_topics, fn podcast_episode_with_topic ->
assert conn.resp_body =~ podcast_episode_with_topic.episode.title
assert conn.resp_body =~ podcast_episode_with_topic.episode.summary
end)
end
end

defp setup_topic_feed_tests(context) do
# Add some topics
t1 = insert(:topic, slug: "rust", name: "Rust", description: "Rust is a systems programming language created by Mozilla.")
# Create some news items
ni1 = insert(:published_news_item, headline: "Rust is pretty cool", story: "Use it or lose it")
# Associate those news items with topics
nit1 = insert(:news_item_topic, topic: t1, news_item: ni1)

# Add a post
post1 = insert(:published_post, body: "Can you believe this Rust thing?!")
pt1 = insert(:post_topic, post: post1, topic: t1)

# Add a podcast
pod1 = insert(:podcast, name: "Rust Crust", slug: "rustcrust", description: "Rust is neat")
# Add a podcast episode
e1 = insert(:published_episode, podcast: pod1)
# Associate that episode with a topic
et1 = insert(:episode_topic, episode: e1, topic: t1)

%{
topics: %{
"rust" => t1
},
news_items_with_topics: [nit1],
posts_with_topics: [pt1],
podcast_episodes_with_topics: [et1]
}
end
Comment on lines +204 to +231
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1) test Topic feed the topic's podcasts feed (ChangelogWeb.FeedControllerTest)
     test/changelog_web/controllers/feed_controller_test.exs:175
     Assertion with =~ failed
     code:  assert conn.resp_body =~ post_with_topic.post.title
     left:  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\">\n  <channel>\n    <title>Changelog: Rust</title>\n    <copyright>All rights reserved</copyright>\n    <link>http://localhost:4001/</link>\n    <atom:link href=\"http://localhost:4001/feed\" rel=\"self\" type=\"application/rss+xml\" />\n    <atom:link href=\"http://localhost:4001/\" rel=\"alternate\" type=\"text/html\" />\n    <language>en-us</language>\n    <description>Rust is a systems programming language created by Mozilla.</description>\n  \n  </channel>\n</rss>\n"
     right: "Post 4"
     stacktrace:
       test/changelog_web/controllers/feed_controller_test.exs:193: anonymous fn/2 in ChangelogWeb.FeedControllerTest."test Topic feed the topic's podcasts feed"/1
       (elixir 1.13.4) lib/enum.ex:937: Enum."-each/2-lists^foreach/1-0-"/2
       test/changelog_web/controllers/feed_controller_test.exs:192: (test)

Currently trying to figure out why this test is failing. I think my test setup with the various ExMachina insert statements isn't set up correctly. 🤔 I'll hack at it some more soon!

end