Implement RSS feeds

This commit is contained in:
Mikko Ahlroth 2018-11-03 18:01:08 +02:00
parent 5942fe2d98
commit 617c510f94
11 changed files with 151 additions and 27 deletions

View file

@ -3,18 +3,23 @@ defmodule Mebe2.Web.Router do
use Raxx.Logger
use Mebe2.Web.Middleware.RequestTime
use Mebe2.Web.Middleware.Archives
alias Mebe2.Web.Routes
use Raxx.Router, [
{%{method: :GET, path: ["tag", _tag, "p", _page]}, Mebe2.Web.Routes.Tag},
{%{method: :GET, path: ["tag", _tag]}, Mebe2.Web.Routes.Tag},
{%{method: :GET, path: ["archive", _year, _month, "p", _page]}, Mebe2.Web.Routes.Month},
{%{method: :GET, path: ["archive", _year, _month]}, Mebe2.Web.Routes.Month},
{%{method: :GET, path: ["archive", _year, "p", _page]}, Mebe2.Web.Routes.Year},
{%{method: :GET, path: ["archive", _year]}, Mebe2.Web.Routes.Year},
{%{method: :GET, path: ["p", _page]}, Mebe2.Web.Routes.Index},
{%{method: :GET, path: [_year, _month, _day, _slug]}, Mebe2.Web.Routes.Post},
{%{method: :GET, path: [_slug]}, Mebe2.Web.Routes.Page},
{%{method: :GET, path: []}, Mebe2.Web.Routes.Index},
{_, Mebe2.Web.Routes.NotFound}
{%{method: :GET, path: ["tag", _tag, "p", _page]}, Routes.Tag},
{%{method: :GET, path: ["tag", _tag, "feed"]}, Routes.Tag},
{%{method: :GET, path: ["tag", _tag]}, Routes.Tag},
{%{method: :GET, path: ["archive", _year, _month, "p", _page]}, Routes.Month},
{%{method: :GET, path: ["archive", _year, _month, "feed"]}, Routes.Month},
{%{method: :GET, path: ["archive", _year, _month]}, Routes.Month},
{%{method: :GET, path: ["archive", _year, "p", _page]}, Routes.Year},
{%{method: :GET, path: ["archive", _year, "feed"]}, Routes.Year},
{%{method: :GET, path: ["archive", _year]}, Routes.Year},
{%{method: :GET, path: ["p", _page]}, Routes.Index},
{%{method: :GET, path: ["feed"]}, Routes.Index},
{%{method: :GET, path: [_year, _month, _day, _slug]}, Routes.Post},
{%{method: :GET, path: [_slug]}, Routes.Page},
{%{method: :GET, path: []}, Routes.Index},
{_, Routes.NotFound}
]
end

View file

@ -1,15 +1,21 @@
defmodule Mebe2.Web.Routes.Index do
use Raxx.Server
alias Mebe2.Web.Views.Index
alias Mebe2.Web.Routes.Utils
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["p", page]} = _req, _state) do
Mebe2.Web.Routes.Utils.render_posts(page, &post_getter/2, &Index.render/4)
Utils.render_posts(page, &post_getter/2, &Index.render/4)
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["feed"]} = _req, _state) do
Utils.render_feed(&post_getter/2)
end
@impl Raxx.Server
def handle_request(%Raxx.Request{} = _req, _state) do
Mebe2.Web.Routes.Utils.render_posts(&post_getter/2, &Index.render/4)
Utils.render_posts(&post_getter/2, &Index.render/4)
end
defp post_getter(first, limit),

View file

@ -1,5 +1,6 @@
defmodule Mebe2.Web.Routes.Month do
use Raxx.Server
alias Mebe2.Web.Routes.Utils
@impl Raxx.Server
def handle_request(
@ -8,13 +9,23 @@ defmodule Mebe2.Web.Routes.Month do
) do
with {year, _} <- Integer.parse(year_str),
{month, _} <- Integer.parse(month_str) do
Mebe2.Web.Routes.Utils.render_posts(
Utils.render_posts(
page,
&post_getter(year, month, &1, &2),
&renderer(year, month, &1, &2, &3, &4)
)
else
_ -> Mebe2.Web.Routes.Utils.render_404()
_ -> Utils.render_404()
end
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["archive", year_str, month_str, "feed"]} = _req, _state) do
with {year, _} <- Integer.parse(year_str),
{month, _} <- Integer.parse(month_str) do
Utils.render_feed(&post_getter(year, month, &1, &2))
else
_ -> Utils.render_404()
end
end
@ -22,12 +33,12 @@ defmodule Mebe2.Web.Routes.Month do
def handle_request(%Raxx.Request{path: ["archive", year_str, month_str]} = _req, _state) do
with {year, _} <- Integer.parse(year_str),
{month, _} <- Integer.parse(month_str) do
Mebe2.Web.Routes.Utils.render_posts(
Utils.render_posts(
&post_getter(year, month, &1, &2),
&renderer(year, month, &1, &2, &3, &4)
)
else
_ -> Mebe2.Web.Routes.Utils.render_404()
_ -> Utils.render_404()
end
end

View file

@ -1,18 +1,24 @@
defmodule Mebe2.Web.Routes.Tag do
use Raxx.Server
alias Mebe2.Web.Routes.Utils
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["tag", tag, "p", page]} = _req, _state) do
Mebe2.Web.Routes.Utils.render_posts(
Utils.render_posts(
page,
&post_getter(tag, &1, &2),
&renderer(tag, &1, &2, &3, &4)
)
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["tag", tag, "feed"]} = _req, _state) do
Utils.render_feed(&post_getter(tag, &1, &2))
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["tag", tag]} = _req, _state) do
Mebe2.Web.Routes.Utils.render_posts(&post_getter(tag, &1, &2), &renderer(tag, &1, &2, &3, &4))
Utils.render_posts(&post_getter(tag, &1, &2), &renderer(tag, &1, &2, &3, &4))
end
defp post_getter(tag, first, limit),

View file

@ -20,10 +20,11 @@ defmodule Mebe2.Web.Routes.Utils do
@doc """
Get post range for the given page, i.e. the starting post and limit to display on that page.
You may optionally give the posts per page limit to use when constructing the range.
"""
@spec get_post_range(integer) :: {integer, integer}
def get_post_range(page) when is_integer(page) do
ppp = Mebe2.get_conf(:posts_per_page)
@spec get_post_range(integer, integer | nil) :: {integer, integer}
def get_post_range(page, posts_per_page \\ nil) when is_integer(page) do
ppp = if is_nil(posts_per_page), do: Mebe2.get_conf(:posts_per_page), else: posts_per_page
{(page - 1) * ppp, ppp}
end
@ -31,7 +32,7 @@ defmodule Mebe2.Web.Routes.Utils do
Render a set of posts on the given page.
- `page` is a string from the URL path.
- `post_getter` isa function that gets the range of posts to load and returns a tuple where the
- `post_getter` is a function that gets the range of posts to load and returns a tuple where the
first element is the list of found posts, and the second element is the amount of all posts
that are in the DB with these conditions.
- `renderer` is a function that renders the content. Its arguments are the response to use,
@ -53,6 +54,26 @@ defmodule Mebe2.Web.Routes.Utils do
end
end
@doc """
Render RSS feed for the given posts.
`post_getter` is a function that gets the range of posts to load and returns a tuple where the
first element is the list of found posts and the second element can be anything (the structure)
is the same as in `render_posts` so that the post getters can be reused.
"""
@spec render_feed((integer, integer -> {[Post.t()], any})) :: Raxx.Response.t()
def render_feed(post_getter) do
with true <- Mebe2.get_conf(:enable_feeds) do
{first, limit} = get_post_range(1, Mebe2.get_conf(:posts_in_feed))
{posts, _} = post_getter.(first, limit)
Raxx.response(200)
|> Mebe2.Web.Views.Feeds.PostList.render(posts)
else
_ -> render_404()
end
end
@doc """
Render a 404 page.
"""

View file

@ -1,28 +1,38 @@
defmodule Mebe2.Web.Routes.Year do
use Raxx.Server
alias Mebe2.Web.Routes.Utils
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["archive", year_str, "p", page]} = _req, _state) do
with {year, _} <- Integer.parse(year_str) do
Mebe2.Web.Routes.Utils.render_posts(
Utils.render_posts(
page,
&post_getter(year, &1, &2),
&renderer(year, &1, &2, &3, &4)
)
else
_ -> Mebe2.Web.Routes.Utils.render_404()
_ -> Utils.render_404()
end
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["archive", year_str, "feed"]} = _req, _state) do
with {year, _} <- Integer.parse(year_str) do
Utils.render_feed(&post_getter(year, &1, &2))
else
_ -> Utils.render_404()
end
end
@impl Raxx.Server
def handle_request(%Raxx.Request{path: ["archive", year_str]} = _req, _state) do
with {year, _} <- Integer.parse(year_str) do
Mebe2.Web.Routes.Utils.render_posts(
Utils.render_posts(
&post_getter(year, &1, &2),
&renderer(year, &1, &2, &3, &4)
)
else
_ -> Mebe2.Web.Routes.Utils.render_404()
_ -> Utils.render_404()
end
end

View file

@ -3,6 +3,15 @@
<head>
<meta charset="utf-8" />
<title><%= Mebe2.get_conf(:blog_name) %></title>
<%= raw(if Mebe2.get_conf(:enable_feeds) do %>
<link
rel="alternate"
type="application/rss+xml"
title="RSS feed"
href="<%= Mebe2.get_conf(:absolute_url) %>/feed"
/>
<% end) %>
</head>
<body>
<header>

View file

@ -0,0 +1,4 @@
defmodule Mebe2.Web.Views.Feeds.BaseLayout do
use Raxx.Layout,
layout: "base_layout.xml.eex"
end

View file

@ -0,0 +1,11 @@
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title><%= Mebe2.get_conf(:blog_name) %></title>
<link><%= Mebe2.get_conf(:absolute_url) %>/</link>
<description></description>
<generator>Mebe2</generator>
<copyright>© <%= Mebe2.get_conf(:blog_author) %></copyright>
<%= __content__ %>
</channel>
</rss>

View file

@ -0,0 +1,13 @@
defmodule Mebe2.Web.Views.Feeds.PostList do
use Mebe2.Web.Views.Feeds.BaseLayout,
template: "post_list.xml.eex",
arguments: [:posts]
@doc """
Format datetime for RSS usage.
"""
@spec format_time(DateTime.t()) :: String.t()
def format_time(datetime) do
Calendar.Strftime.strftime!(datetime, "%a, %d %b %Y %T %z")
end
end

View file

@ -0,0 +1,28 @@
<%= raw(for post <- posts do %>
<item>
<title>
<![CDATA[<%= raw(post.title) %>]]>
</title>
<%= raw(if Mebe2.get_conf(:multi_author_mode) do %>
<dc:creator><%= Mebe2.Engine.Utils.get_author(post) %></dc:creator>
<% end) %>
<link><%= Mebe2.get_conf(:absolute_url) %><%= Mebe2.Web.Views.Utils.get_post_path(post) %></link>
<guid><%= Mebe2.get_conf(:absolute_url) %><%= Mebe2.Web.Views.Utils.get_post_path(post) %></guid>
<description>
<![CDATA[
<%= raw(if Mebe2.get_conf(:show_full_content) do %>
<%= raw(post.content) %>
<% else %>
<%= raw(post.short_content) %>
<% end) %>
<a href="<%= Mebe2.get_conf(:absolute_url) %><%= Mebe2.Web.Views.Utils.get_post_path(post) %>">
Read more
</a>
]]>
</description>
<pubDate><%= format_time(post.datetime) %></pubDate>
</item>
<% end) %>