Files
firehose/blogex/lib/blogex/router.ex
T

201 lines
4.6 KiB
Elixir

defmodule Blogex.Router do
@moduledoc """
Plug router that serves blog pages and feeds.
Serves HTML to browsers (Accept: text/html) and JSON to API clients.
Mount this in your host app's router:
# In your Phoenix router
scope "/blog" do
pipe_through :browser
forward "/engineering", Blogex.Router, blog: MyApp.EngineeringBlog
forward "/releases", Blogex.Router, blog: MyApp.ReleaseNotes
end
Or use the convenience macro:
import Blogex.Router, only: [blogex_routes: 2]
scope "/blog" do
pipe_through :browser
blogex_routes "/engineering", MyApp.EngineeringBlog
blogex_routes "/releases", MyApp.ReleaseNotes
end
## Routes served
* `GET /` — post index (paginated)
* `GET /:slug` — individual post
* `GET /tag/:tag` — posts by tag
* `GET /feed.xml` — RSS feed
* `GET /atom.xml` — Atom feed
"""
use Plug.Router
plug :match
plug :dispatch
get "/feed.xml" do
blog = conn.private[:blogex_blog]
base_url = Blogex.Router.Helpers.base_url(conn)
xml = Blogex.Feed.rss(blog, base_url)
conn
|> put_resp_content_type("application/rss+xml")
|> send_resp(200, xml)
end
get "/atom.xml" do
blog = conn.private[:blogex_blog]
base_url = Blogex.Router.Helpers.base_url(conn)
xml = Blogex.Feed.atom(blog, base_url)
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, xml)
end
get "/tag/:tag" do
blog = conn.private[:blogex_blog]
posts = blog.posts_by_tag(tag)
if wants_html?(conn) do
assigns = %{
blog_title: blog.title(),
tag: tag,
posts: posts,
base_path: blog.base_path()
}
send_html(conn, Blogex.Layout.tag_page(assigns))
else
conn
|> put_resp_content_type("application/json")
|> send_resp(
200,
Jason.encode!(%{
blog: blog.blog_id(),
tag: tag,
posts: Enum.map(posts, &post_json/1)
})
)
end
end
get "/:slug" do
blog = conn.private[:blogex_blog]
case blog.get_post(slug) do
nil ->
conn |> send_resp(404, "Post not found")
post ->
if wants_html?(conn) do
assigns = %{post: post, base_path: blog.base_path()}
send_html(conn, Blogex.Layout.show_page(assigns))
else
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(post_json(post)))
end
end
end
get "/" do
blog = conn.private[:blogex_blog]
page = (conn.params["page"] || "1") |> String.to_integer()
result = blog.paginate(page)
if wants_html?(conn) do
assigns = %{
blog_title: blog.title(),
blog_description: blog.description(),
posts: result.entries,
base_path: blog.base_path(),
page: result.page,
total_pages: result.total_pages
}
send_html(conn, Blogex.Layout.index_page(assigns))
else
conn
|> put_resp_content_type("application/json")
|> send_resp(
200,
Jason.encode!(%{
blog: blog.blog_id(),
title: blog.title(),
posts: Enum.map(result.entries, &post_json/1),
page: result.page,
total_pages: result.total_pages,
total_entries: result.total_entries
})
)
end
end
match _ do
send_resp(conn, 404, "Not found")
end
@doc false
def init(opts), do: opts
@doc false
def call(conn, opts) do
blog = Keyword.fetch!(opts, :blog)
conn
|> Plug.Conn.put_private(:blogex_blog, blog)
|> super(opts)
end
defp wants_html?(conn) do
case Plug.Conn.get_req_header(conn, "accept") do
[accept | _] -> String.contains?(accept, "text/html")
_ -> false
end
end
defp send_html(conn, rendered) do
html =
rendered
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
conn
|> put_resp_content_type("text/html")
|> send_resp(200, html)
end
defp post_json(post) do
%{
id: post.id,
title: post.title,
author: post.author,
date: Date.to_iso8601(post.date),
description: post.description,
tags: post.tags,
body: post.body
}
end
defmodule Helpers do
@moduledoc false
def base_url(conn) do
scheme = if conn.scheme == :https, do: "https", else: "http"
port_suffix = port_suffix(conn.scheme, conn.port)
"#{scheme}://#{conn.host}#{port_suffix}"
end
defp port_suffix(:http, 80), do: ""
defp port_suffix(:https, 443), do: ""
defp port_suffix(_, port), do: ":#{port}"
end
end