Static blog with front page summary
Goal: have a personal blog, and try out another point in the 'modular app design with elixir' space. Designing OTP systems with elixir had some interesting ideas.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
defmodule Blogex.Feed do
|
||||
@moduledoc """
|
||||
Generates RSS 2.0 and Atom feeds for a blog.
|
||||
|
||||
## Usage
|
||||
|
||||
# In a controller or plug:
|
||||
xml = Blogex.Feed.rss(MyApp.EngineeringBlog, "https://myapp.com")
|
||||
conn |> put_resp_content_type("application/rss+xml") |> send_resp(200, xml)
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Generates an RSS 2.0 XML feed for the given blog module.
|
||||
|
||||
## Options
|
||||
|
||||
* `:limit` - max number of posts to include (default: 20)
|
||||
* `:language` - feed language (default: "en-us")
|
||||
"""
|
||||
def rss(blog_module, base_url, opts \\ []) do
|
||||
limit = Keyword.get(opts, :limit, 20)
|
||||
language = Keyword.get(opts, :language, "en-us")
|
||||
posts = Enum.take(blog_module.all_posts(), limit)
|
||||
|
||||
blog_url = "#{base_url}#{blog_module.base_path()}"
|
||||
feed_url = "#{blog_url}/feed.xml"
|
||||
|
||||
pub_date =
|
||||
case posts do
|
||||
[latest | _] -> format_rfc822(latest.date)
|
||||
[] -> format_rfc822(Date.utc_today())
|
||||
end
|
||||
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
||||
<channel>
|
||||
<title>#{escape(blog_module.title())}</title>
|
||||
<link>#{blog_url}</link>
|
||||
<description>#{escape(blog_module.description())}</description>
|
||||
<language>#{language}</language>
|
||||
<pubDate>#{pub_date}</pubDate>
|
||||
<atom:link href="#{feed_url}" rel="self" type="application/rss+xml"/>
|
||||
#{Enum.map_join(posts, "\n", &item_xml(&1, base_url, blog_module))}
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|> String.trim()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates an Atom feed for the given blog module.
|
||||
|
||||
## Options
|
||||
|
||||
* `:limit` - max number of posts to include (default: 20)
|
||||
"""
|
||||
def atom(blog_module, base_url, opts \\ []) do
|
||||
limit = Keyword.get(opts, :limit, 20)
|
||||
posts = Enum.take(blog_module.all_posts(), limit)
|
||||
|
||||
blog_url = "#{base_url}#{blog_module.base_path()}"
|
||||
feed_url = "#{blog_url}/feed.xml"
|
||||
|
||||
updated =
|
||||
case posts do
|
||||
[latest | _] -> format_iso8601(latest.date)
|
||||
[] -> format_iso8601(Date.utc_today())
|
||||
end
|
||||
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>#{escape(blog_module.title())}</title>
|
||||
<link href="#{blog_url}" rel="alternate"/>
|
||||
<link href="#{feed_url}" rel="self"/>
|
||||
<id>#{blog_url}</id>
|
||||
<updated>#{updated}</updated>
|
||||
#{Enum.map_join(posts, "\n", &entry_xml(&1, base_url, blog_module))}
|
||||
</feed>
|
||||
"""
|
||||
|> String.trim()
|
||||
end
|
||||
|
||||
# Private helpers
|
||||
|
||||
defp item_xml(post, base_url, blog_module) do
|
||||
url = "#{base_url}#{blog_module.base_path()}/#{post.id}"
|
||||
|
||||
"""
|
||||
<item>
|
||||
<title>#{escape(post.title)}</title>
|
||||
<link>#{url}</link>
|
||||
<guid isPermaLink="true">#{url}</guid>
|
||||
<pubDate>#{format_rfc822(post.date)}</pubDate>
|
||||
<description>#{escape(post.description)}</description>
|
||||
<content:encoded><![CDATA[#{post.body}]]></content:encoded>
|
||||
#{Enum.map_join(post.tags, "\n", &" <category>#{escape(&1)}</category>")}
|
||||
</item>
|
||||
"""
|
||||
end
|
||||
|
||||
defp entry_xml(post, base_url, blog_module) do
|
||||
url = "#{base_url}#{blog_module.base_path()}/#{post.id}"
|
||||
|
||||
"""
|
||||
<entry>
|
||||
<title>#{escape(post.title)}</title>
|
||||
<link href="#{url}" rel="alternate"/>
|
||||
<id>#{url}</id>
|
||||
<published>#{format_iso8601(post.date)}</published>
|
||||
<updated>#{format_iso8601(post.date)}</updated>
|
||||
<author><name>#{escape(post.author)}</name></author>
|
||||
<summary>#{escape(post.description)}</summary>
|
||||
<content type="html"><![CDATA[#{post.body}]]></content>
|
||||
#{Enum.map_join(post.tags, "\n", &" <category term=\"#{escape(&1)}\"/>")}
|
||||
</entry>
|
||||
"""
|
||||
end
|
||||
|
||||
defp format_rfc822(date) do
|
||||
date
|
||||
|> DateTime.new!(~T[00:00:00], "Etc/UTC")
|
||||
|> Calendar.strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||
end
|
||||
|
||||
defp format_iso8601(date) do
|
||||
"#{Date.to_iso8601(date)}T00:00:00Z"
|
||||
end
|
||||
|
||||
defp escape(text) when is_binary(text) do
|
||||
text
|
||||
|> String.replace("&", "&")
|
||||
|> String.replace("<", "<")
|
||||
|> String.replace(">", ">")
|
||||
|> String.replace("\"", """)
|
||||
|> String.replace("'", "'")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user